{"thread":{"id":"22246","subject":"[PATCH v2 0/2] Show submodules as modified when they contain a dirty work tree","startedAt":"2010-01-16T17:39:55Z","lastAt":"2010-01-16T17:42:53Z","messageCount":3,"participants":["Jens Lehmann"],"isPatch":true,"patchVersion":2,"patchTotal":2},"messages":[{"id":"131887","messageId":"4B51F9EB.5090202@web.de","threadId":"22246","inReplyTo":null,"subject":"[PATCH v2 0/2] Show submodules as modified when they contain a dirty work tree","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2010-01-16T17:39:55Z","receivedAt":"2010-01-16T17:39:55Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Here is the updated version of the two patches.\n\nThis fixes all issues raised and passes the test suite.\n\n\nJens Lehmann (1):\n  Show submodules as modified when they contain a dirty work tree\n\nJunio C Hamano (1):\n  Teach diff that modified submodule directory is dirty\n\n diff-lib.c                  |    8 +++-\n diff.c                      |    9 ++++-\n submodule.c                 |   49 +++++++++++++++++++++++++\n submodule.h                 |    1 +\n t/t4027-diff-submodule.sh   |   84 ++++++++++++++++++++++++++++++++++++++++++-\n t/t7506-status-submodule.sh |   83 ++++++++++++++++++++++++++++++++++--------\n 6 files changed, 214 insertions(+), 20 deletions(-)\n"},{"id":"131888","messageId":"4B51FA80.8040109@web.de","threadId":"22246","inReplyTo":"4B51F9EB.5090202@web.de","subject":"[PATCH v2 1/2] Show submodules as modified when they contain a dirty work tree","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2010-01-16T17:42:24Z","receivedAt":"2010-01-16T17:42:24Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Until now a submodule only then showed up as modified in the supermodule\nwhen the last commit in the submodule differed from the one in the index\nor the diffed against commit of the superproject. A dirty work tree\ncontaining new untracked or modified files in a submodule was\nundetectable when looking at it from the superproject.\n\nNow git status and git diff (against the work tree) in the superproject\nwill also display submodules as modified when they contain untracked or\nmodified files, even if the compared ref matches the HEAD of the\nsubmodule.\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\nSigned-off-by: Nanako Shiraishi <nanako3@lavabit.com>\n---\n\nChanged since the last version:\n- Added missing include (spotted by Junio)\n- Made test 7506 more robust (thanks to Nanako)\n- When diffing the work tree against a commit in the supermodule the\n  submodule did not show up as modified when the refs matched\n\n\n diff-lib.c                  |    8 +++-\n submodule.c                 |   49 +++++++++++++++++++++++++\n submodule.h                 |    1 +\n t/t7506-status-submodule.sh |   83 +++++++++++++++++++++++++++++++++++--------\n 4 files changed, 124 insertions(+), 17 deletions(-)\n\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 274ff8c..29c5915 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -10,6 +10,7 @@\n #include \"cache-tree.h\"\n #include \"unpack-trees.h\"\n #include \"refs.h\"\n+#include \"submodule.h\"\n\n /*\n  * diff-files\n@@ -159,7 +160,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t\t\tcontinue;\n \t\t}\n\n-\t\tif (ce_uptodate(ce) || ce_skip_worktree(ce))\n+\t\tif ((ce_uptodate(ce) && !S_ISGITLINK(ce->ce_mode)) || ce_skip_worktree(ce))\n \t\t\tcontinue;\n\n \t\t/* If CE_VALID is set, don't look at workdir for file removal */\n@@ -176,6 +177,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t\tcontinue;\n \t\t}\n \t\tchanged = ce_match_stat(ce, &st, ce_option);\n+\t\tif (S_ISGITLINK(ce->ce_mode) && !changed)\n+\t\t\tchanged = is_submodule_modified(ce->name);\n \t\tif (!changed) {\n \t\t\tce_mark_uptodate(ce);\n \t\t\tif (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER))\n@@ -230,7 +233,8 @@ static int get_stat_data(struct cache_entry *ce,\n \t\t\treturn -1;\n \t\t}\n \t\tchanged = ce_match_stat(ce, &st, 0);\n-\t\tif (changed) {\n+\t\tif (changed\n+\t\t    || (S_ISGITLINK(ce->ce_mode) && is_submodule_modified(ce->name))) {\n \t\t\tmode = ce_mode_from_stat(ce, st.st_mode);\n \t\t\tsha1 = null_sha1;\n \t\t}\ndiff --git a/submodule.c b/submodule.c\nindex 86aad65..3f851de 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -4,6 +4,7 @@\n #include \"diff.h\"\n #include \"commit.h\"\n #include \"revision.h\"\n+#include \"run-command.h\"\n\n int add_submodule_odb(const char *path)\n {\n@@ -112,3 +113,51 @@ void show_submodule_summary(FILE *f, const char *path,\n \t}\n \tstrbuf_release(&sb);\n }\n+\n+int is_submodule_modified(const char *path)\n+{\n+\tint len;\n+\tstruct child_process cp;\n+\tconst char *argv[] = {\n+\t\t\"status\",\n+\t\t\"--porcelain\",\n+\t\tNULL,\n+\t};\n+\tchar *env[3];\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf, \"%s/.git/\", path);\n+\tif (!is_directory(buf.buf)) {\n+\t\tstrbuf_release(&buf);\n+\t\t/* The submodule is not checked out, so it is not modified */\n+\t\treturn 0;\n+\n+\t}\n+\tstrbuf_reset(&buf);\n+\n+\tstrbuf_addf(&buf, \"GIT_WORK_TREE=%s\", path);\n+\tenv[0] = strbuf_detach(&buf, NULL);\n+\tstrbuf_addf(&buf, \"GIT_DIR=%s/.git\", path);\n+\tenv[1] = strbuf_detach(&buf, NULL);\n+\tenv[2] = NULL;\n+\n+\tmemset(&cp, 0, sizeof(cp));\n+\tcp.argv = argv;\n+\tcp.env = (const char *const *)env;\n+\tcp.git_cmd = 1;\n+\tcp.no_stdin = 1;\n+\tcp.out = -1;\n+\tif (start_command(&cp))\n+\t\tdie(\"Could not run git status --porcelain\");\n+\n+\tlen = strbuf_read(&buf, cp.out, 1024);\n+\tclose(cp.out);\n+\n+\tif (finish_command(&cp))\n+\t\tdie(\"git status --porcelain failed\");\n+\n+\tfree(env[0]);\n+\tfree(env[1]);\n+\tstrbuf_release(&buf);\n+\treturn len != 0;\n+}\ndiff --git a/submodule.h b/submodule.h\nindex 4c0269d..0773121 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -4,5 +4,6 @@\n void show_submodule_summary(FILE *f, const char *path,\n \t\tunsigned char one[20], unsigned char two[20],\n \t\tconst char *del, const char *add, const char *reset);\n+int is_submodule_modified(const char *path);\n\n #endif\ndiff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh\nindex 3ca17ab..253c334 100755\n--- a/t/t7506-status-submodule.sh\n+++ b/t/t7506-status-submodule.sh\n@@ -5,34 +5,87 @@ test_description='git status for submodule'\n . ./test-lib.sh\n\n test_expect_success 'setup' '\n-\ttest_create_repo sub\n-\tcd sub &&\n-\t: >bar &&\n-\tgit add bar &&\n-\tgit commit -m \" Add bar\" &&\n-\tcd .. &&\n-\tgit add sub &&\n+\ttest_create_repo sub &&\n+\t(\n+\t\tcd sub &&\n+\t\t: >bar &&\n+\t\tgit add bar &&\n+\t\tgit commit -m \" Add bar\" &&\n+\t\t: >foo &&\n+\t\tgit add foo &&\n+\t\tgit commit -m \" Add foo\"\n+\t) &&\n+\techo output > .gitignore &&\n+\tgit add sub .gitignore &&\n \tgit commit -m \"Add submodule sub\"\n '\n\n test_expect_success 'status clean' '\n-\tgit status |\n-\tgrep \"nothing to commit\"\n+\tgit status >output &&\n+\tgrep \"nothing to commit\" output\n '\n+\n test_expect_success 'commit --dry-run -a clean' '\n-\tgit commit --dry-run -a |\n-\tgrep \"nothing to commit\"\n+\ttest_must_fail git commit --dry-run -a >output &&\n+\tgrep \"nothing to commit\" output\n+'\n+\n+test_expect_success 'status with modified file in submodule' '\n+\t(cd sub && git reset --hard) &&\n+\techo \"changed\" >sub/foo &&\n+\tgit status >output &&\n+\tgrep \"modified:   sub\" output\n+'\n+\n+test_expect_success 'status with modified file in submodule (porcelain)' '\n+\t(cd sub && git reset --hard) &&\n+\techo \"changed\" >sub/foo &&\n+\tgit status --porcelain >output &&\n+\tdiff output - <<-\\EOF\n+\t M sub\n+\tEOF\n+'\n+\n+test_expect_success 'status with added file in submodule' '\n+\t(cd sub && git reset --hard && echo >foo && git add foo) &&\n+\tgit status >output &&\n+\tgrep \"modified:   sub\" output\n+'\n+\n+test_expect_success 'status with added file in submodule (porcelain)' '\n+\t(cd sub && git reset --hard && echo >foo && git add foo) &&\n+\tgit status --porcelain >output &&\n+\tdiff output - <<-\\EOF\n+\t M sub\n+\tEOF\n+'\n+\n+test_expect_success 'status with untracked file in submodule' '\n+\t(cd sub && git reset --hard) &&\n+\techo \"content\" >sub/new-file &&\n+\tgit status >output &&\n+\tgrep \"modified:   sub\" output\n+'\n+\n+test_expect_success 'status with untracked file in submodule (porcelain)' '\n+\tgit status --porcelain >output &&\n+\tdiff output - <<-\\EOF\n+\t M sub\n+\tEOF\n '\n+\n test_expect_success 'rm submodule contents' '\n \trm -rf sub/* sub/.git\n '\n+\n test_expect_success 'status clean (empty submodule dir)' '\n-\tgit status |\n-\tgrep \"nothing to commit\"\n+\tgit status >output &&\n+\tgrep \"nothing to commit\" output\n '\n+\n test_expect_success 'status -a clean (empty submodule dir)' '\n-\tgit commit --dry-run -a |\n-\tgrep \"nothing to commit\"\n+\ttest_must_fail git commit --dry-run -a >output &&\n+\tgrep \"nothing to commit\" output\n '\n\n test_done\n-- \n1.6.6.332.g1985b\n"},{"id":"131889","messageId":"4B51FA9D.7070206@web.de","threadId":"22246","inReplyTo":"4B51F9EB.5090202@web.de","subject":"[PATCH v2 2/2] Teach diff that modified submodule directory is dirty","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2010-01-16T17:42:53Z","receivedAt":"2010-01-16T17:42:53Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nA diff run in superproject only compares the name of the commit object\nbound at the submodule paths.  When we compare with a work tree and the\nchecked out submodule directory is dirty (e.g. has either staged or\nunstaged changes, or has new files the user forgot to add to the index),\nshow the work tree side as \"dirty\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n---\n\nChanged since the last version:\n- I added 3 tests for \"git diff HEAD\" when refs match (the HEAD in the\n  submodule is the same as last committed in the superproject)\n\n\n diff.c                    |    9 ++++-\n t/t4027-diff-submodule.sh |   84 ++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 90 insertions(+), 3 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 5060b48..012b3d3 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -2029,9 +2029,14 @@ static int populate_from_stdin(struct diff_filespec *s)\n static int diff_populate_gitlink(struct diff_filespec *s, int size_only)\n {\n \tint len;\n-\tchar *data = xmalloc(100);\n+\tchar *data = xmalloc(100), *dirty = \"\";\n+\n+\t/* Are we looking at the work tree? */\n+\tif (!s->sha1_valid && is_submodule_modified(s->path))\n+\t\tdirty = \"-dirty\";\n+\n \tlen = snprintf(data, 100,\n-\t\t\"Subproject commit %s\\n\", sha1_to_hex(s->sha1));\n+\t\t       \"Subproject commit %s%s\\n\", sha1_to_hex(s->sha1), dirty);\n \ts->data = data;\n \ts->size = len;\n \ts->should_free = 1;\ndiff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh\nindex 5cf8924..83c1914 100755\n--- a/t/t4027-diff-submodule.sh\n+++ b/t/t4027-diff-submodule.sh\n@@ -32,7 +32,8 @@ test_expect_success setup '\n \t\tcd sub &&\n \t\tgit rev-list HEAD\n \t) &&\n-\techo \":160000 160000 $3 $_z40 M\tsub\" >expect\n+\techo \":160000 160000 $3 $_z40 M\tsub\" >expect &&\n+\tsubtip=$3 subprev=$2\n '\n\n test_expect_success 'git diff --raw HEAD' '\n@@ -50,6 +51,87 @@ test_expect_success 'git diff-files --raw' '\n \ttest_cmp expect actual.files\n '\n\n+expect_from_to () {\n+\tprintf \"%sSubproject commit %s\\n+Subproject commit %s\\n\" \\\n+\t\t\"-\" \"$1\" \"$2\"\n+}\n+\n+test_expect_success 'git diff HEAD' '\n+\tgit diff HEAD >actual &&\n+\tsed -e \"1,/^@@/d\" actual >actual.body &&\n+\texpect_from_to >expect.body $subtip $subprev &&\n+\ttest_cmp expect.body actual.body\n+'\n+\n+test_expect_success 'git diff HEAD with dirty submodule (work tree)' '\n+\techo >>sub/world &&\n+\tgit diff HEAD >actual &&\n+\tsed -e \"1,/^@@/d\" actual >actual.body &&\n+\texpect_from_to >expect.body $subtip $subprev-dirty &&\n+\ttest_cmp expect.body actual.body\n+'\n+\n+test_expect_success 'git diff HEAD with dirty submodule (index)' '\n+\t(\n+\t\tcd sub &&\n+\t\tgit reset --hard &&\n+\t\techo >>world &&\n+\t\tgit add world\n+\t) &&\n+\tgit diff HEAD >actual &&\n+\tsed -e \"1,/^@@/d\" actual >actual.body &&\n+\texpect_from_to >expect.body $subtip $subprev-dirty &&\n+\ttest_cmp expect.body actual.body\n+'\n+\n+test_expect_success 'git diff HEAD with dirty submodule (untracked)' '\n+\t(\n+\t\tcd sub &&\n+\t\tgit reset --hard &&\n+\t\tgit clean -qfdx &&\n+\t\t>cruft\n+\t) &&\n+\tgit diff HEAD >actual &&\n+\tsed -e \"1,/^@@/d\" actual >actual.body &&\n+\texpect_from_to >expect.body $subtip $subprev-dirty &&\n+\ttest_cmp expect.body actual.body\n+'\n+\n+test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' '\n+\tgit commit -m \"x\" sub &&\n+\techo >>sub/world &&\n+\tgit diff HEAD >actual &&\n+\tsed -e \"1,/^@@/d\" actual >actual.body &&\n+\texpect_from_to >expect.body $subprev $subprev-dirty &&\n+\ttest_cmp expect.body actual.body\n+'\n+\n+test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' '\n+\t(\n+\t\tcd sub &&\n+\t\tgit reset --hard &&\n+\t\techo >>world &&\n+\t\tgit add world\n+\t) &&\n+\tgit diff HEAD >actual &&\n+\tsed -e \"1,/^@@/d\" actual >actual.body &&\n+\texpect_from_to >expect.body $subprev $subprev-dirty &&\n+\ttest_cmp expect.body actual.body\n+'\n+\n+test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' '\n+\t(\n+\t\tcd sub &&\n+\t\tgit reset --hard &&\n+\t\tgit clean -qfdx &&\n+\t\t>cruft\n+\t) &&\n+\tgit diff HEAD >actual &&\n+\tsed -e \"1,/^@@/d\" actual >actual.body &&\n+\texpect_from_to >expect.body $subprev $subprev-dirty &&\n+\ttest_cmp expect.body actual.body\n+'\n+\n test_expect_success 'git diff (empty submodule dir)' '\n \t: >empty &&\n \trm -rf sub/* sub/.git &&\n-- \n1.6.6.332.g1985b\n"}]}