зеркало из https://github.com/microsoft/git.git
Merge branch 'sb/submodule-short-status'
The output from "git status --short" has been extended to show various kinds of dirtyness in submodules differently; instead of to "M" for modified, 'm' and '?' can be shown to signal changes only to the working tree of the submodule but not the commit that is checked out. * sb/submodule-short-status: submodule.c: correctly handle nested submodules in is_submodule_modified short status: improve reporting for submodule changes submodule.c: stricter checking for submodules in is_submodule_modified submodule.c: port is_submodule_modified to use porcelain 2 submodule.c: convert is_submodule_modified to use strbuf_getwholeline submodule.c: factor out early loop termination in is_submodule_modified submodule.c: use argv_array in is_submodule_modified
This commit is contained in:
Коммит
c703555cc8
|
@ -181,6 +181,17 @@ in which case `XY` are `!!`.
|
|||
! ! ignored
|
||||
-------------------------------------------------
|
||||
|
||||
Submodules have more state and instead report
|
||||
M the submodule has a different HEAD than
|
||||
recorded in the index
|
||||
m the submodule has modified content
|
||||
? the submodule has untracked files
|
||||
since modified content or untracked files in a submodule cannot be added
|
||||
via `git add` in the superproject to prepare a commit.
|
||||
|
||||
'm' and '?' are applied recursively. For example if a nested submodule
|
||||
in a submodule contains an untracked file, this is reported as '?' as well.
|
||||
|
||||
If -b is used the short-format status is preceded by a line
|
||||
|
||||
## branchname tracking info
|
||||
|
@ -210,6 +221,8 @@ field from the first filename). Third, filenames containing special
|
|||
characters are not specially formatted; no quoting or
|
||||
backslash-escaping is performed.
|
||||
|
||||
Any submodule changes are reported as modified `M` instead of `m` or single `?`.
|
||||
|
||||
Porcelain Format Version 2
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
81
submodule.c
81
submodule.c
|
@ -1112,67 +1112,78 @@ out:
|
|||
|
||||
unsigned is_submodule_modified(const char *path, int ignore_untracked)
|
||||
{
|
||||
ssize_t len;
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
const char *argv[] = {
|
||||
"status",
|
||||
"--porcelain",
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
FILE *fp;
|
||||
unsigned dirty_submodule = 0;
|
||||
const char *line, *next_line;
|
||||
const char *git_dir;
|
||||
int ignore_cp_exit_code = 0;
|
||||
|
||||
strbuf_addf(&buf, "%s/.git", path);
|
||||
git_dir = read_gitfile(buf.buf);
|
||||
if (!git_dir)
|
||||
git_dir = buf.buf;
|
||||
if (!is_directory(git_dir)) {
|
||||
if (!is_git_directory(git_dir)) {
|
||||
if (is_directory(git_dir))
|
||||
die(_("'%s' not recognized as a git repository"), git_dir);
|
||||
strbuf_release(&buf);
|
||||
/* The submodule is not checked out, so it is not modified */
|
||||
return 0;
|
||||
|
||||
}
|
||||
strbuf_reset(&buf);
|
||||
|
||||
argv_array_pushl(&cp.args, "status", "--porcelain=2", NULL);
|
||||
if (ignore_untracked)
|
||||
argv[2] = "-uno";
|
||||
argv_array_push(&cp.args, "-uno");
|
||||
|
||||
cp.argv = argv;
|
||||
prepare_submodule_repo_env(&cp.env_array);
|
||||
cp.git_cmd = 1;
|
||||
cp.no_stdin = 1;
|
||||
cp.out = -1;
|
||||
cp.dir = path;
|
||||
if (start_command(&cp))
|
||||
die("Could not run 'git status --porcelain' in submodule %s", path);
|
||||
die("Could not run 'git status --porcelain=2' in submodule %s", path);
|
||||
|
||||
len = strbuf_read(&buf, cp.out, 1024);
|
||||
line = buf.buf;
|
||||
while (len > 2) {
|
||||
if ((line[0] == '?') && (line[1] == '?')) {
|
||||
fp = xfdopen(cp.out, "r");
|
||||
while (strbuf_getwholeline(&buf, fp, '\n') != EOF) {
|
||||
/* regular untracked files */
|
||||
if (buf.buf[0] == '?')
|
||||
dirty_submodule |= DIRTY_SUBMODULE_UNTRACKED;
|
||||
if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
|
||||
break;
|
||||
} else {
|
||||
dirty_submodule |= DIRTY_SUBMODULE_MODIFIED;
|
||||
if (ignore_untracked ||
|
||||
(dirty_submodule & DIRTY_SUBMODULE_UNTRACKED))
|
||||
break;
|
||||
}
|
||||
next_line = strchr(line, '\n');
|
||||
if (!next_line)
|
||||
break;
|
||||
next_line++;
|
||||
len -= (next_line - line);
|
||||
line = next_line;
|
||||
}
|
||||
close(cp.out);
|
||||
|
||||
if (finish_command(&cp))
|
||||
die("'git status --porcelain' failed in submodule %s", path);
|
||||
if (buf.buf[0] == 'u' ||
|
||||
buf.buf[0] == '1' ||
|
||||
buf.buf[0] == '2') {
|
||||
/* T = line type, XY = status, SSSS = submodule state */
|
||||
if (buf.len < strlen("T XY SSSS"))
|
||||
die("BUG: invalid status --porcelain=2 line %s",
|
||||
buf.buf);
|
||||
|
||||
if (buf.buf[5] == 'S' && buf.buf[8] == 'U')
|
||||
/* nested untracked file */
|
||||
dirty_submodule |= DIRTY_SUBMODULE_UNTRACKED;
|
||||
|
||||
if (buf.buf[0] == 'u' ||
|
||||
buf.buf[0] == '2' ||
|
||||
memcmp(buf.buf + 5, "S..U", 4))
|
||||
/* other change */
|
||||
dirty_submodule |= DIRTY_SUBMODULE_MODIFIED;
|
||||
}
|
||||
|
||||
if ((dirty_submodule & DIRTY_SUBMODULE_MODIFIED) &&
|
||||
((dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) ||
|
||||
ignore_untracked)) {
|
||||
/*
|
||||
* We're not interested in any further information from
|
||||
* the child any more, neither output nor its exit code.
|
||||
*/
|
||||
ignore_cp_exit_code = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
if (finish_command(&cp) && !ignore_cp_exit_code)
|
||||
die("'git status --porcelain=2' failed in submodule %s", path);
|
||||
|
||||
strbuf_release(&buf);
|
||||
return dirty_submodule;
|
||||
|
|
|
@ -268,6 +268,14 @@ cat >expect.modified <<EOF
|
|||
M submod
|
||||
EOF
|
||||
|
||||
cat >expect.modified_inside <<EOF
|
||||
m submod
|
||||
EOF
|
||||
|
||||
cat >expect.modified_untracked <<EOF
|
||||
? submod
|
||||
EOF
|
||||
|
||||
cat >expect.cached <<EOF
|
||||
D submod
|
||||
EOF
|
||||
|
@ -421,7 +429,7 @@ test_expect_success 'rm of a populated submodule with modifications fails unless
|
|||
test -d submod &&
|
||||
test -f submod/.git &&
|
||||
git status -s -uno --ignore-submodules=none >actual &&
|
||||
test_cmp expect.modified actual &&
|
||||
test_cmp expect.modified_inside actual &&
|
||||
git rm -f submod &&
|
||||
test ! -d submod &&
|
||||
git status -s -uno --ignore-submodules=none >actual &&
|
||||
|
@ -436,7 +444,7 @@ test_expect_success 'rm of a populated submodule with untracked files fails unle
|
|||
test -d submod &&
|
||||
test -f submod/.git &&
|
||||
git status -s -uno --ignore-submodules=none >actual &&
|
||||
test_cmp expect.modified actual &&
|
||||
test_cmp expect.modified_untracked actual &&
|
||||
git rm -f submod &&
|
||||
test ! -d submod &&
|
||||
git status -s -uno --ignore-submodules=none >actual &&
|
||||
|
@ -621,7 +629,7 @@ test_expect_success 'rm of a populated nested submodule with different nested HE
|
|||
test -d submod &&
|
||||
test -f submod/.git &&
|
||||
git status -s -uno --ignore-submodules=none >actual &&
|
||||
test_cmp expect.modified actual &&
|
||||
test_cmp expect.modified_inside actual &&
|
||||
git rm -f submod &&
|
||||
test ! -d submod &&
|
||||
git status -s -uno --ignore-submodules=none >actual &&
|
||||
|
@ -636,7 +644,7 @@ test_expect_success 'rm of a populated nested submodule with nested modification
|
|||
test -d submod &&
|
||||
test -f submod/.git &&
|
||||
git status -s -uno --ignore-submodules=none >actual &&
|
||||
test_cmp expect.modified actual &&
|
||||
test_cmp expect.modified_inside actual &&
|
||||
git rm -f submod &&
|
||||
test ! -d submod &&
|
||||
git status -s -uno --ignore-submodules=none >actual &&
|
||||
|
@ -651,7 +659,7 @@ test_expect_success 'rm of a populated nested submodule with nested untracked fi
|
|||
test -d submod &&
|
||||
test -f submod/.git &&
|
||||
git status -s -uno --ignore-submodules=none >actual &&
|
||||
test_cmp expect.modified actual &&
|
||||
test_cmp expect.modified_untracked actual &&
|
||||
git rm -f submod &&
|
||||
test ! -d submod &&
|
||||
git status -s -uno --ignore-submodules=none >actual &&
|
||||
|
|
|
@ -17,6 +17,12 @@ test_create_repo_with_commit () {
|
|||
)
|
||||
}
|
||||
|
||||
sanitize_output () {
|
||||
sed -e "s/$_x40/HASH/" -e "s/$_x40/HASH/" output >output2 &&
|
||||
mv output2 output
|
||||
}
|
||||
|
||||
|
||||
test_expect_success 'setup' '
|
||||
test_create_repo_with_commit sub &&
|
||||
echo output > .gitignore &&
|
||||
|
@ -50,6 +56,15 @@ test_expect_success 'status with modified file in submodule (porcelain)' '
|
|||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status with modified file in submodule (short)' '
|
||||
(cd sub && git reset --hard) &&
|
||||
echo "changed" >sub/foo &&
|
||||
git status --short >output &&
|
||||
diff output - <<-\EOF
|
||||
m sub
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status with added file in submodule' '
|
||||
(cd sub && git reset --hard && echo >foo && git add foo) &&
|
||||
git status >output &&
|
||||
|
@ -64,6 +79,14 @@ test_expect_success 'status with added file in submodule (porcelain)' '
|
|||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status with added file in submodule (short)' '
|
||||
(cd sub && git reset --hard && echo >foo && git add foo) &&
|
||||
git status --short >output &&
|
||||
diff output - <<-\EOF
|
||||
m sub
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status with untracked file in submodule' '
|
||||
(cd sub && git reset --hard) &&
|
||||
echo "content" >sub/new-file &&
|
||||
|
@ -83,6 +106,13 @@ test_expect_success 'status with untracked file in submodule (porcelain)' '
|
|||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status with untracked file in submodule (short)' '
|
||||
git status --short >output &&
|
||||
diff output - <<-\EOF
|
||||
? sub
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status with added and untracked file in submodule' '
|
||||
(cd sub && git reset --hard && echo >foo && git add foo) &&
|
||||
echo "content" >sub/new-file &&
|
||||
|
@ -177,8 +207,24 @@ test_expect_success 'status with added file in modified submodule with .git file
|
|||
test_i18ngrep "modified: sub (new commits, modified content)" output
|
||||
'
|
||||
|
||||
test_expect_success 'status with a lot of untracked files in the submodule' '
|
||||
(
|
||||
cd sub
|
||||
i=0 &&
|
||||
while test $i -lt 1024
|
||||
do
|
||||
>some-file-$i
|
||||
i=$(( $i + 1 ))
|
||||
done
|
||||
) &&
|
||||
git status --porcelain sub 2>err.actual &&
|
||||
test_must_be_empty err.actual &&
|
||||
rm err.actual
|
||||
'
|
||||
|
||||
test_expect_success 'rm submodule contents' '
|
||||
rm -rf sub/* sub/.git
|
||||
rm -rf sub &&
|
||||
mkdir sub
|
||||
'
|
||||
|
||||
test_expect_success 'status clean (empty submodule dir)' '
|
||||
|
@ -271,4 +317,91 @@ test_expect_success 'diff --submodule with merge conflict in .gitmodules' '
|
|||
test_cmp diff_submodule_actual diff_submodule_expect
|
||||
'
|
||||
|
||||
# We'll setup different cases for further testing:
|
||||
# sub1 will contain a nested submodule,
|
||||
# sub2 will have an untracked file
|
||||
# sub3 will have an untracked repository
|
||||
test_expect_success 'setup superproject with untracked file in nested submodule' '
|
||||
(
|
||||
cd super &&
|
||||
git clean -dfx &&
|
||||
rm .gitmodules &&
|
||||
git submodule add -f ./sub1 &&
|
||||
git submodule add -f ./sub2 &&
|
||||
git submodule add -f ./sub1 sub3 &&
|
||||
git commit -a -m "messy merge in superproject" &&
|
||||
(
|
||||
cd sub1 &&
|
||||
git submodule add ../sub2 &&
|
||||
git commit -a -m "add sub2 to sub1"
|
||||
) &&
|
||||
git add sub1 &&
|
||||
git commit -a -m "update sub1 to contain nested sub"
|
||||
) &&
|
||||
echo content >super/sub1/sub2/file &&
|
||||
echo content >super/sub2/file &&
|
||||
git -C super/sub3 clone ../../sub2 untracked_repository
|
||||
'
|
||||
|
||||
test_expect_success 'status with untracked file in nested submodule (porcelain)' '
|
||||
git -C super status --porcelain >output &&
|
||||
diff output - <<-\EOF
|
||||
M sub1
|
||||
M sub2
|
||||
M sub3
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status with untracked file in nested submodule (porcelain=2)' '
|
||||
git -C super status --porcelain=2 >output &&
|
||||
sanitize_output output &&
|
||||
diff output - <<-\EOF
|
||||
1 .M S..U 160000 160000 160000 HASH HASH sub1
|
||||
1 .M S..U 160000 160000 160000 HASH HASH sub2
|
||||
1 .M S..U 160000 160000 160000 HASH HASH sub3
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status with untracked file in nested submodule (short)' '
|
||||
git -C super status --short >output &&
|
||||
diff output - <<-\EOF
|
||||
? sub1
|
||||
? sub2
|
||||
? sub3
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'setup superproject with modified file in nested submodule' '
|
||||
git -C super/sub1/sub2 add file &&
|
||||
git -C super/sub2 add file
|
||||
'
|
||||
|
||||
test_expect_success 'status with added file in nested submodule (porcelain)' '
|
||||
git -C super status --porcelain >output &&
|
||||
diff output - <<-\EOF
|
||||
M sub1
|
||||
M sub2
|
||||
M sub3
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status with added file in nested submodule (porcelain=2)' '
|
||||
git -C super status --porcelain=2 >output &&
|
||||
sanitize_output output &&
|
||||
diff output - <<-\EOF
|
||||
1 .M S.M. 160000 160000 160000 HASH HASH sub1
|
||||
1 .M S.M. 160000 160000 160000 HASH HASH sub2
|
||||
1 .M S..U 160000 160000 160000 HASH HASH sub3
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status with added file in nested submodule (short)' '
|
||||
git -C super status --short >output &&
|
||||
diff output - <<-\EOF
|
||||
m sub1
|
||||
m sub2
|
||||
? sub3
|
||||
EOF
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
17
wt-status.c
17
wt-status.c
|
@ -407,6 +407,16 @@ static void wt_longstatus_print_change_data(struct wt_status *s,
|
|||
strbuf_release(&twobuf);
|
||||
}
|
||||
|
||||
static char short_submodule_status(struct wt_status_change_data *d) {
|
||||
if (d->new_submodule_commits)
|
||||
return 'M';
|
||||
if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
|
||||
return 'm';
|
||||
if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
|
||||
return '?';
|
||||
return d->worktree_status;
|
||||
}
|
||||
|
||||
static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
|
||||
struct diff_options *options,
|
||||
void *data)
|
||||
|
@ -431,10 +441,13 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
|
|||
}
|
||||
if (!d->worktree_status)
|
||||
d->worktree_status = p->status;
|
||||
d->dirty_submodule = p->two->dirty_submodule;
|
||||
if (S_ISGITLINK(p->two->mode))
|
||||
if (S_ISGITLINK(p->two->mode)) {
|
||||
d->dirty_submodule = p->two->dirty_submodule;
|
||||
d->new_submodule_commits = !!oidcmp(&p->one->oid,
|
||||
&p->two->oid);
|
||||
if (s->status_format == STATUS_FORMAT_SHORT)
|
||||
d->worktree_status = short_submodule_status(d);
|
||||
}
|
||||
|
||||
switch (p->status) {
|
||||
case DIFF_STATUS_ADDED:
|
||||
|
|
Загрузка…
Ссылка в новой задаче