aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2017-04-19 21:37:12 -0700
committerJunio C Hamano <gitster@pobox.com>2017-04-19 21:37:12 -0700
commitc703555cc89cf6aedf549a1233b242d8cb8e0f20 (patch)
treed886e03a2f5acab43e1425c91c7aaa6aa8268c49
parent584f8975d2d9530a34bd0b936ae774f82fe30fed (diff)
parent40069d6e3a195c8c2414ea60ed75b84ae47f88a1 (diff)
downloadgit-c703555cc89cf6aedf549a1233b242d8cb8e0f20.tar.gz
git-c703555cc89cf6aedf549a1233b242d8cb8e0f20.tar.xz
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
-rw-r--r--Documentation/git-status.txt13
-rw-r--r--submodule.c75
-rwxr-xr-xt/t3600-rm.sh18
-rwxr-xr-xt/t7506-status-submodule.sh135
-rw-r--r--wt-status.c17
5 files changed, 218 insertions, 40 deletions
diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt
index ba873657c..d70abc6af 100644
--- a/Documentation/git-status.txt
+++ b/Documentation/git-status.txt
@@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/submodule.c b/submodule.c
index 7c3c4b17f..2ee970572 100644
--- a/submodule.c
+++ b/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;
+
+ 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;
}
- next_line = strchr(line, '\n');
- if (!next_line)
+
+ 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;
- next_line++;
- len -= (next_line - line);
- line = next_line;
+ }
}
- close(cp.out);
+ fclose(fp);
- if (finish_command(&cp))
- die("'git status --porcelain' failed in submodule %s", path);
+ if (finish_command(&cp) && !ignore_cp_exit_code)
+ die("'git status --porcelain=2' failed in submodule %s", path);
strbuf_release(&buf);
return dirty_submodule;
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
index 3c6345572..5f9913ba3 100755
--- a/t/t3600-rm.sh
+++ b/t/t3600-rm.sh
@@ -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 &&
diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh
index d31b34da8..055c90736 100755
--- a/t/t7506-status-submodule.sh
+++ b/t/t7506-status-submodule.sh
@@ -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
diff --git a/wt-status.c b/wt-status.c
index 308cf3779..037548496 100644
--- a/wt-status.c
+++ b/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: