{"thread":{"id":"22917","subject":"[PATCH] git status: Show detailed dirty status of submodules in long format","startedAt":"2010-03-05T11:29:29Z","lastAt":"2010-03-08T12:53:19Z","messageCount":5,"participants":["Jens Lehmann","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"136201","messageId":"4B90EB19.2070300@web.de","threadId":"22917","inReplyTo":null,"subject":"[PATCH] git status: Show detailed dirty status of submodules in long format","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2010-03-05T11:29:29Z","receivedAt":"2010-03-05T11:29:29Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Since 1.7.0 there a three reasons a submodule is considered modified\nagainst the work tree: It contains new commits, modified content or\nuntracked content. Lets show all reasons in the long format of git status,\nso the user can better asses the nature of the modification. This change\ndoes not affect the short and porcelain formats.\n\nTwo new members are added to \"struct wt_status_change_data\" to store the\ninformation gathered by run_diff_files(). wt-status.c uses the new flag\nDIFF_FORMAT_SUBMODULES to tell diff-lib.c it wants to get detailed dirty\ninformation about submodules.\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n---\n\n\nSo here is the promised patch teaching \"git status\" to show explicit\ndirtiness information for submodules. Instead of just showing a\nsubmodule as modified, it prints the reason(s) why the submodule is\nconsidered modified along with a new hint line (thanks to Sergio\nCallegari for bringing that last one up):\n\n# Changed but not updated:\n#   (use \"git add <file>...\" to update what will be committed)\n#   (use \"git checkout -- <file>...\" to discard changes in working directory)\n#   (commit or discard the untracked or modified content in submodules)\n#\n#\tmodified:   sub (new commits, modified content, untracked content)\n\n(Note: The part in behind the submodule name is not colored like\n\"modified:\" and the name itself but like the header above for better\nvisibility)\n\n\nI am not so proud of DIFF_FORMAT_DIRTY_SUBMODULES, the new flag i\nintroduced to tell run_diff_files() it should gather the information\nneeded even if we don't want patch output. It isn't a \"format\" per se,\nbut i couldn't come up with a better way to do this. Opinions?\n\n\n\n diff-lib.c                  |    6 ++++--\n diff.h                      |    1 +\n t/t7506-status-submodule.sh |    6 +++---\n t/t7508-status.sh           |   11 +++++++++++\n wt-status.c                 |   26 +++++++++++++++++++++++++-\n wt-status.h                 |    2 ++\n 6 files changed, 46 insertions(+), 6 deletions(-)\n\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 15ca7cd..e8b143d 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -180,7 +180,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)\n \t\tchanged = ce_match_stat(ce, &st, ce_option);\n \t\tif (S_ISGITLINK(ce->ce_mode)\n \t\t    && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES)\n-\t\t    && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH))) {\n+\t\t    && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH)\n+\t\t        || (revs->diffopt.output_format & DIFF_FORMAT_DIRTY_SUBMODULES))) {\n \t\t\tdirty_submodule = is_submodule_modified(ce->name);\n \t\t\tif (dirty_submodule)\n \t\t\t\tchanged = 1;\n@@ -243,7 +244,8 @@ static int get_stat_data(struct cache_entry *ce,\n \t\tchanged = ce_match_stat(ce, &st, 0);\n \t\tif (S_ISGITLINK(ce->ce_mode)\n \t\t    && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES)\n-\t\t    && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH))) {\n+\t\t    && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH)\n+\t\t        || (diffopt->output_format & DIFF_FORMAT_DIRTY_SUBMODULES))) {\n \t\t\t*dirty_submodule = is_submodule_modified(ce->name);\n \t\t\tif (*dirty_submodule)\n \t\t\t\tchanged = 1;\ndiff --git a/diff.h b/diff.h\nindex 2ef3341..04fcde5 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -44,6 +44,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,\n #define DIFF_FORMAT_NO_OUTPUT\t0x0800\n\n #define DIFF_FORMAT_CALLBACK\t0x1000\n+#define DIFF_FORMAT_DIRTY_SUBMODULES\t0x2000\n\n #define DIFF_OPT_RECURSIVE           (1 <<  0)\n #define DIFF_OPT_TREE_IN_RECURSIVE   (1 <<  1)\ndiff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh\nindex 253c334..1c8d32a 100755\n--- a/t/t7506-status-submodule.sh\n+++ b/t/t7506-status-submodule.sh\n@@ -34,7 +34,7 @@ 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+\tgrep \"modified:   sub (new commits, modified content)\" output\n '\n\n test_expect_success 'status with modified file in submodule (porcelain)' '\n@@ -49,7 +49,7 @@ test_expect_success 'status with modified file in submodule (porcelain)' '\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+\tgrep \"modified:   sub (new commits, modified content)\" output\n '\n\n test_expect_success 'status with added file in submodule (porcelain)' '\n@@ -64,7 +64,7 @@ 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+\tgrep \"modified:   sub (new commits, untracked content)\" output\n '\n\n test_expect_success 'status with untracked file in submodule (porcelain)' '\ndiff --git a/t/t7508-status.sh b/t/t7508-status.sh\nindex 556d0fa..47b5672 100755\n--- a/t/t7508-status.sh\n+++ b/t/t7508-status.sh\n@@ -47,6 +47,7 @@ cat >expect <<\\EOF\n # Changed but not updated:\n #   (use \"git add <file>...\" to update what will be committed)\n #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#   (commit or discard the untracked or modified content in submodules)\n #\n #\tmodified:   dir1/modified\n #\n@@ -96,6 +97,7 @@ cat >expect <<EOF\n # Changed but not updated:\n #   (use \"git add <file>...\" to update what will be committed)\n #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#   (commit or discard the untracked or modified content in submodules)\n #\n #\tmodified:   dir1/modified\n #\n@@ -141,6 +143,7 @@ cat >expect <<EOF\n # Changed but not updated:\n #   (use \"git add <file>...\" to update what will be committed)\n #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#   (commit or discard the untracked or modified content in submodules)\n #\n #\tmodified:   dir1/modified\n #\n@@ -199,6 +202,7 @@ cat >expect <<EOF\n # Changed but not updated:\n #   (use \"git add <file>...\" to update what will be committed)\n #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#   (commit or discard the untracked or modified content in submodules)\n #\n #\tmodified:   dir1/modified\n #\n@@ -259,6 +263,7 @@ cat >expect <<\\EOF\n # Changed but not updated:\n #   (use \"git add <file>...\" to update what will be committed)\n #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#   (commit or discard the untracked or modified content in submodules)\n #\n #\tmodified:   modified\n #\n@@ -331,6 +336,7 @@ cat >expect <<\\EOF\n # Changed but not updated:\n #   (use \"git add <file>...\" to update what will be committed)\n #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#   (commit or discard the untracked or modified content in submodules)\n #\n #\t<RED>modified:   dir1/modified<RESET>\n #\n@@ -434,6 +440,7 @@ cat >expect <<\\EOF\n # Changed but not updated:\n #   (use \"git add <file>...\" to update what will be committed)\n #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#   (commit or discard the untracked or modified content in submodules)\n #\n #\tmodified:   dir1/modified\n #\n@@ -517,6 +524,7 @@ cat >expect <<EOF\n # Changed but not updated:\n #   (use \"git add <file>...\" to update what will be committed)\n #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#   (commit or discard the untracked or modified content in submodules)\n #\n #\tmodified:   dir1/modified\n #\n@@ -576,6 +584,7 @@ cat >expect <<EOF\n # Changed but not updated:\n #   (use \"git add <file>...\" to update what will be committed)\n #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#   (commit or discard the untracked or modified content in submodules)\n #\n #\tmodified:   dir1/modified\n #\n@@ -621,6 +630,7 @@ cat >expect <<EOF\n # Changed but not updated:\n #   (use \"git add <file>...\" to update what will be committed)\n #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#   (commit or discard the untracked or modified content in submodules)\n #\n #\tmodified:   dir1/modified\n #\n@@ -669,6 +679,7 @@ cat >expect <<EOF\n # Changed but not updated:\n #   (use \"git add <file>...\" to update what will be committed)\n #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#   (commit or discard the untracked or modified content in submodules)\n #\n #\tmodified:   dir1/modified\n #\ndiff --git a/wt-status.c b/wt-status.c\nindex 5807fc3..a5fc16d 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -90,6 +90,7 @@ static void wt_status_print_dirty_header(struct wt_status *s,\n \telse\n \t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add/rm <file>...\\\" to update what will be committed)\");\n \tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git checkout -- <file>...\\\" to discard changes in working directory)\");\n+\tcolor_fprintf_ln(s->fp, c, \"#   (commit or discard the untracked or modified content in submodules)\");\n \tcolor_fprintf_ln(s->fp, c, \"#\");\n }\n\n@@ -144,6 +145,7 @@ static void wt_status_print_change_data(struct wt_status *s,\n \tchar *two_name;\n \tconst char *one, *two;\n \tstruct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;\n+\tstruct strbuf extra = STRBUF_INIT;\n\n \tone_name = two_name = it->string;\n \tswitch (change_type) {\n@@ -153,6 +155,21 @@ static void wt_status_print_change_data(struct wt_status *s,\n \t\t\tone_name = d->head_path;\n \t\tbreak;\n \tcase WT_STATUS_CHANGED:\n+\t\tif (d->new_submodule_commits || d->dirty_submodule) {\n+\t\t\tconst char *sep = \"\";\n+\t\t\tstrbuf_addstr(&extra, \" (\");\n+\t\t\tif (d->new_submodule_commits) {\n+\t\t\t\tstrbuf_addf(&extra, \"new commits\");\n+\t\t\t\tsep = \", \";\n+\t\t\t}\n+\t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) {\n+\t\t\t\tstrbuf_addf(&extra, \"%smodified content\", sep);\n+\t\t\t\tsep = \", \";\n+\t\t\t}\n+\t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)\n+\t\t\t\tstrbuf_addf(&extra, \"%suntracked content\",sep);\n+\t\t\tstrbuf_addch(&extra, ')');\n+\t\t}\n \t\tstatus = d->worktree_status;\n \t\tbreak;\n \t}\n@@ -189,6 +206,10 @@ static void wt_status_print_change_data(struct wt_status *s,\n \tdefault:\n \t\tdie(\"bug: unhandled diff status %c\", status);\n \t}\n+\tif (extra.len) {\n+\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), extra.buf);\n+\t\tstrbuf_release(&extra);\n+\t}\n \tfprintf(s->fp, \"\\n\");\n \tstrbuf_release(&onebuf);\n \tstrbuf_release(&twobuf);\n@@ -218,6 +239,9 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,\n \t\t}\n \t\tif (!d->worktree_status)\n \t\t\td->worktree_status = p->status;\n+\t\td->dirty_submodule = p->two->dirty_submodule;\n+\t\tif (S_ISGITLINK(p->two->mode))\n+\t\t\td->new_submodule_commits = !!hashcmp(p->one->sha1, p->two->sha1);\n \t}\n }\n\n@@ -280,7 +304,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)\n\n \tinit_revisions(&rev, NULL);\n \tsetup_revisions(0, NULL, &rev, NULL);\n-\trev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;\n+\trev.diffopt.output_format |= DIFF_FORMAT_CALLBACK | DIFF_FORMAT_DIRTY_SUBMODULES;\n \trev.diffopt.format_callback = wt_status_collect_changed_cb;\n \trev.diffopt.format_callback_data = s;\n \trev.prune_data = s->pathspec;\ndiff --git a/wt-status.h b/wt-status.h\nindex c60f40a..9120673 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -25,6 +25,8 @@ struct wt_status_change_data {\n \tint index_status;\n \tint stagemask;\n \tchar *head_path;\n+\tunsigned dirty_submodule       : 2;\n+\tunsigned new_submodule_commits : 1;\n };\n\n struct wt_status {\n-- \n1.7.0.1.326.gc367c.dirty\n"},{"id":"136292","messageId":"7vtyssadtx.fsf@alter.siamese.dyndns.org","threadId":"22917","inReplyTo":"4B90EB19.2070300@web.de","subject":"Re: [PATCH] git status: Show detailed dirty status of submodules in long format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-03-07T05:41:14Z","receivedAt":"2010-03-07T05:41:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jens Lehmann <Jens.Lehmann@web.de> writes:\n\n> # Changed but not updated:\n> #   (use \"git add <file>...\" to update what will be committed)\n> #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n> #   (commit or discard the untracked or modified content in submodules)\n\nCan we do this additional line only when there is a submodule involved in\nthe changes?  The whole point of \"collect first and then print\" is so that\nwe can compute things like \"has_deleted\" before starting to emit any\noutput to intelligently give an appropriate advice message, and it feels\nsilly to say submodules to users who don't even use any submodule.  I have\na suspicion that the majority of users may not even know nor care what a\nsubmodule is.\n\n> I am not so proud of DIFF_FORMAT_DIRTY_SUBMODULES, the new flag i\n> introduced to tell run_diff_files() it should gather the information\n> needed even if we don't want patch output. It isn't a \"format\" per se,\n> but i couldn't come up with a better way to do this. Opinions?\n\nIt indeed does sound like DIFF_OPT_* than DIFF_FORMAT_*.  In any case, we\nprobably would want to have a small helper function that encapsulates this\npart that appear twice:\n\n\tchanged = ce_match_stat(ce, &st, ce_option);\n\tif (S_ISGITLINK(ce->ce_mode)\n\t    && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES)\n\t    && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH)\n\t\t|| (revs->diffopt.output_format & DIFF_FORMAT_DIRTY_SUBMODULES))) {\n\t\tdirty_submodule = is_submodule_modified(ce->name);\n\t\tif (dirty_submodule)\n\t\t\tchanged = 1;\n\t}\n\nto something like\n\n\tchanged = match_stat_with_submodule(&revs->diffopt, ce, &st,\n        \t\t\t\t\tce_option, &dirty_submodule);\n\nand the implementation of match_stat_with_submodule() a bit more heavily\ncommented so that people will know what it is for.\n"},{"id":"136350","messageId":"4B9406A5.2090300@web.de","threadId":"22917","inReplyTo":"7vtyssadtx.fsf@alter.siamese.dyndns.org","subject":"[PATCH v2] git status: Show detailed dirty status of submodules in long format","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2010-03-07T20:03:49Z","receivedAt":"2010-03-07T20:03:49Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Since 1.7.0 there a three reasons a submodule is considered modified\nagainst the work tree: It contains new commits, modified content or\nuntracked content. Lets show all reasons in the long format of git status,\nso the user can better asses the nature of the modification. This change\ndoes not affect the short and porcelain formats.\n\nTwo new members are added to \"struct wt_status_change_data\" to store the\ninformation gathered by run_diff_files(). wt-status.c uses the new flag\nDIFF_OPT_DIRTY_SUBMODULES to tell diff-lib.c it wants to get detailed\ndirty information about submodules.\n\nA hint line for submodules is printed in the dirty header when dirty\nsubmodules are present.\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n---\n\nAm 07.03.2010 06:41, schrieb Junio C Hamano:\n> Jens Lehmann <Jens.Lehmann@web.de> writes:\n> \n>> # Changed but not updated:\n>> #   (use \"git add <file>...\" to update what will be committed)\n>> #   (use \"git checkout -- <file>...\" to discard changes in working directory)\n>> #   (commit or discard the untracked or modified content in submodules)\n> \n> Can we do this additional line only when there is a submodule involved in\n> the changes?  The whole point of \"collect first and then print\" is so that\n> we can compute things like \"has_deleted\" before starting to emit any\n> output to intelligently give an appropriate advice message, and it feels\n> silly to say submodules to users who don't even use any submodule.  I have\n> a suspicion that the majority of users may not even know nor care what a\n> submodule is.\n\nGood point, the line will only be printed when necessary.\n\n\n>> I am not so proud of DIFF_FORMAT_DIRTY_SUBMODULES, the new flag i\n>> introduced to tell run_diff_files() it should gather the information\n>> needed even if we don't want patch output. It isn't a \"format\" per se,\n>> but i couldn't come up with a better way to do this. Opinions?\n> \n> It indeed does sound like DIFF_OPT_* than DIFF_FORMAT_*.\n\nRight, using DIFF_OPT_DIRTY_SUBMODULES now.\n\n\n> In any case, we\n> probably would want to have a small helper function that encapsulates this\n> part that appear twice:\n> \n> \tchanged = ce_match_stat(ce, &st, ce_option);\n> \tif (S_ISGITLINK(ce->ce_mode)\n> \t    && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES)\n> \t    && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH)\n> \t\t|| (revs->diffopt.output_format & DIFF_FORMAT_DIRTY_SUBMODULES))) {\n> \t\tdirty_submodule = is_submodule_modified(ce->name);\n> \t\tif (dirty_submodule)\n> \t\t\tchanged = 1;\n> \t}\n> \n> to something like\n> \n> \tchanged = match_stat_with_submodule(&revs->diffopt, ce, &st,\n>         \t\t\t\t\tce_option, &dirty_submodule);\n> \n> and the implementation of match_stat_with_submodule() a bit more heavily\n> commented so that people will know what it is for.\n\nI was planning a cleanup patch anyway to get rid of the test for\nDIFF_FORMAT_PATCH by setting DIFF_OPT_DIRTY_SUBMODULES at the appropriate\ncall sites and then only test for DIFF_OPT_DIRTY_SUBMODULES here. Putting\nthe duplicated code into a new function is a good idea, so I'll do that\nin the cleanup patch, o.k.?\n\n\n\n diff-lib.c                  |    6 +++-\n diff.h                      |    1 +\n t/t7506-status-submodule.sh |    6 ++--\n wt-status.c                 |   47 ++++++++++++++++++++++++++++++++++++------\n wt-status.h                 |    2 +\n 5 files changed, 50 insertions(+), 12 deletions(-)\n\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 15ca7cd..63f8fcc 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -180,7 +180,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)\n \t\tchanged = ce_match_stat(ce, &st, ce_option);\n \t\tif (S_ISGITLINK(ce->ce_mode)\n \t\t    && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES)\n-\t\t    && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH))) {\n+\t\t    && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH)\n+\t\t        || DIFF_OPT_TST(&revs->diffopt, DIRTY_SUBMODULES))) {\n \t\t\tdirty_submodule = is_submodule_modified(ce->name);\n \t\t\tif (dirty_submodule)\n \t\t\t\tchanged = 1;\n@@ -243,7 +244,8 @@ static int get_stat_data(struct cache_entry *ce,\n \t\tchanged = ce_match_stat(ce, &st, 0);\n \t\tif (S_ISGITLINK(ce->ce_mode)\n \t\t    && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES)\n-\t\t    && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH))) {\n+\t\t    && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH)\n+\t\t        || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) {\n \t\t\t*dirty_submodule = is_submodule_modified(ce->name);\n \t\t\tif (*dirty_submodule)\n \t\t\t\tchanged = 1;\ndiff --git a/diff.h b/diff.h\nindex 2ef3341..95ed7f8 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -69,6 +69,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,\n #define DIFF_OPT_ALLOW_TEXTCONV      (1 << 21)\n #define DIFF_OPT_DIFF_FROM_CONTENTS  (1 << 22)\n #define DIFF_OPT_SUBMODULE_LOG       (1 << 23)\n+#define DIFF_OPT_DIRTY_SUBMODULES    (1 << 24)\n\n #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)\n #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)\ndiff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh\nindex 253c334..1c8d32a 100755\n--- a/t/t7506-status-submodule.sh\n+++ b/t/t7506-status-submodule.sh\n@@ -34,7 +34,7 @@ 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+\tgrep \"modified:   sub (new commits, modified content)\" output\n '\n\n test_expect_success 'status with modified file in submodule (porcelain)' '\n@@ -49,7 +49,7 @@ test_expect_success 'status with modified file in submodule (porcelain)' '\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+\tgrep \"modified:   sub (new commits, modified content)\" output\n '\n\n test_expect_success 'status with added file in submodule (porcelain)' '\n@@ -64,7 +64,7 @@ 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+\tgrep \"modified:   sub (new commits, untracked content)\" output\n '\n\n test_expect_success 'status with untracked file in submodule (porcelain)' '\ndiff --git a/wt-status.c b/wt-status.c\nindex 5807fc3..c028afd 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -78,7 +78,8 @@ static void wt_status_print_cached_header(struct wt_status *s)\n }\n\n static void wt_status_print_dirty_header(struct wt_status *s,\n-\t\t\t\t\t int has_deleted)\n+\t\t\t\t\t int has_deleted,\n+\t\t\t\t\t int has_dirty_submodules)\n {\n \tconst char *c = color(WT_STATUS_HEADER, s);\n\n@@ -90,6 +91,8 @@ static void wt_status_print_dirty_header(struct wt_status *s,\n \telse\n \t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add/rm <file>...\\\" to update what will be committed)\");\n \tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git checkout -- <file>...\\\" to discard changes in working directory)\");\n+\tif (has_dirty_submodules)\n+\t\tcolor_fprintf_ln(s->fp, c, \"#   (commit or discard the untracked or modified content in submodules)\");\n \tcolor_fprintf_ln(s->fp, c, \"#\");\n }\n\n@@ -144,6 +147,7 @@ static void wt_status_print_change_data(struct wt_status *s,\n \tchar *two_name;\n \tconst char *one, *two;\n \tstruct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;\n+\tstruct strbuf extra = STRBUF_INIT;\n\n \tone_name = two_name = it->string;\n \tswitch (change_type) {\n@@ -153,6 +157,21 @@ static void wt_status_print_change_data(struct wt_status *s,\n \t\t\tone_name = d->head_path;\n \t\tbreak;\n \tcase WT_STATUS_CHANGED:\n+\t\tif (d->new_submodule_commits || d->dirty_submodule) {\n+\t\t\tconst char *sep = \"\";\n+\t\t\tstrbuf_addstr(&extra, \" (\");\n+\t\t\tif (d->new_submodule_commits) {\n+\t\t\t\tstrbuf_addf(&extra, \"new commits\");\n+\t\t\t\tsep = \", \";\n+\t\t\t}\n+\t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) {\n+\t\t\t\tstrbuf_addf(&extra, \"%smodified content\", sep);\n+\t\t\t\tsep = \", \";\n+\t\t\t}\n+\t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)\n+\t\t\t\tstrbuf_addf(&extra, \"%suntracked content\",sep);\n+\t\t\tstrbuf_addch(&extra, ')');\n+\t\t}\n \t\tstatus = d->worktree_status;\n \t\tbreak;\n \t}\n@@ -189,6 +208,10 @@ static void wt_status_print_change_data(struct wt_status *s,\n \tdefault:\n \t\tdie(\"bug: unhandled diff status %c\", status);\n \t}\n+\tif (extra.len) {\n+\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), extra.buf);\n+\t\tstrbuf_release(&extra);\n+\t}\n \tfprintf(s->fp, \"\\n\");\n \tstrbuf_release(&onebuf);\n \tstrbuf_release(&twobuf);\n@@ -218,6 +241,9 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,\n \t\t}\n \t\tif (!d->worktree_status)\n \t\t\td->worktree_status = p->status;\n+\t\td->dirty_submodule = p->two->dirty_submodule;\n+\t\tif (S_ISGITLINK(p->two->mode))\n+\t\t\td->new_submodule_commits = !!hashcmp(p->one->sha1, p->two->sha1);\n \t}\n }\n\n@@ -281,6 +307,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)\n \tinit_revisions(&rev, NULL);\n \tsetup_revisions(0, NULL, &rev, NULL);\n \trev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;\n+\tDIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);\n \trev.diffopt.format_callback = wt_status_collect_changed_cb;\n \trev.diffopt.format_callback_data = s;\n \trev.prune_data = s->pathspec;\n@@ -418,33 +445,39 @@ static void wt_status_print_updated(struct wt_status *s)\n  *  0 : no change\n  *  1 : some change but no delete\n  */\n-static int wt_status_check_worktree_changes(struct wt_status *s)\n+static int wt_status_check_worktree_changes(struct wt_status *s,\n+\t\t\t\t\t     int *dirty_submodules)\n {\n \tint i;\n \tint changes = 0;\n\n+\t*dirty_submodules = 0;\n+\n \tfor (i = 0; i < s->change.nr; i++) {\n \t\tstruct wt_status_change_data *d;\n \t\td = s->change.items[i].util;\n \t\tif (!d->worktree_status ||\n \t\t    d->worktree_status == DIFF_STATUS_UNMERGED)\n \t\t\tcontinue;\n-\t\tchanges = 1;\n+\t\tif (!changes)\n+\t\t\tchanges = 1;\n+\t\tif (d->dirty_submodule)\n+\t\t\t*dirty_submodules = 1;\n \t\tif (d->worktree_status == DIFF_STATUS_DELETED)\n-\t\t\treturn -1;\n+\t\t\tchanges = -1;\n \t}\n \treturn changes;\n }\n\n static void wt_status_print_changed(struct wt_status *s)\n {\n-\tint i;\n-\tint worktree_changes = wt_status_check_worktree_changes(s);\n+\tint i, dirty_submodules;\n+\tint worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);\n\n \tif (!worktree_changes)\n \t\treturn;\n\n-\twt_status_print_dirty_header(s, worktree_changes < 0);\n+\twt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules);\n\n \tfor (i = 0; i < s->change.nr; i++) {\n \t\tstruct wt_status_change_data *d;\ndiff --git a/wt-status.h b/wt-status.h\nindex c60f40a..9120673 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -25,6 +25,8 @@ struct wt_status_change_data {\n \tint index_status;\n \tint stagemask;\n \tchar *head_path;\n+\tunsigned dirty_submodule       : 2;\n+\tunsigned new_submodule_commits : 1;\n };\n\n struct wt_status {\n-- \n1.7.0.1.326.g01b9ac\n"},{"id":"136378","messageId":"7vvdd7yzzb.fsf@alter.siamese.dyndns.org","threadId":"22917","inReplyTo":"4B9406A5.2090300@web.de","subject":"Re: [PATCH v2] git status: Show detailed dirty status of submodules in long format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-03-08T08:33:28Z","receivedAt":"2010-03-08T08:33:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jens Lehmann <Jens.Lehmann@web.de> writes:\n\n> I was planning a cleanup patch anyway to get rid of the test for\n> DIFF_FORMAT_PATCH by setting DIFF_OPT_DIRTY_SUBMODULES at the appropriate\n> call sites and then only test for DIFF_OPT_DIRTY_SUBMODULES here. Putting\n> the duplicated code into a new function is a good idea, so I'll do that\n> in the cleanup patch, o.k.?\n\nSounds sensible, thanks.\n\n> diff --git a/wt-status.c b/wt-status.c\n> index 5807fc3..c028afd 100644\n> --- a/wt-status.c\n> +++ b/wt-status.c\n> @@ -153,6 +157,21 @@ static void wt_status_print_change_data(struct wt_status *s,\n>  \t\t\tone_name = d->head_path;\n>  \t\tbreak;\n>  \tcase WT_STATUS_CHANGED:\n> +\t\tif (d->new_submodule_commits || d->dirty_submodule) {\n> +\t\t\tconst char *sep = \"\";\n> +\t\t\tstrbuf_addstr(&extra, \" (\");\n> +\t\t\tif (d->new_submodule_commits) {\n> +\t\t\t\tstrbuf_addf(&extra, \"new commits\");\n> +\t\t\t\tsep = \", \";\n> +\t\t\t}\n> +\t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) {\n> +\t\t\t\tstrbuf_addf(&extra, \"%smodified content\", sep);\n> +\t\t\t\tsep = \", \";\n> +\t\t\t}\n> +\t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)\n> +\t\t\t\tstrbuf_addf(&extra, \"%suntracked content\",sep);\n> +\t\t\tstrbuf_addch(&extra, ')');\n> +\t\t}\n\nThis may be easier to read and maintain if you always added \", \" at each\nstep, and then backed up by two places before closing the thing with ')',\nwithout doing the sep = \", \" in the middle.\n\n> @@ -189,6 +208,10 @@ static void wt_status_print_change_data(struct wt_status *s,\n>  \tdefault:\n>  \t\tdie(\"bug: unhandled diff status %c\", status);\n>  \t}\n> +\tif (extra.len) {\n> +\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), extra.buf);\n> +\t\tstrbuf_release(&extra);\n> +\t}\n\nThis needs to be touched up as:\n\n\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"%s\", extra.buf);\n\njust in case we may start adding some more free strings in extra.buf\nlater.  Also we tell the compiler that the third parameter to the function\nquacks like printf format string, and get a warning for passing an\narbitrary string extra.buf to it.\n"},{"id":"136388","messageId":"4B94F33F.2010208@web.de","threadId":"22917","inReplyTo":"7vvdd7yzzb.fsf@alter.siamese.dyndns.org","subject":"[PATCH v3] git status: Show detailed dirty status of submodules in long format","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2010-03-08T12:53:19Z","receivedAt":"2010-03-08T12:53:19Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"Since 1.7.0 there are three reasons a submodule is considered modified\nagainst the work tree: It contains new commits, modified content or\nuntracked content. Lets show all reasons in the long format of git status,\nso the user can better asses the nature of the modification. This change\ndoes not affect the short and porcelain formats.\n\nTwo new members are added to \"struct wt_status_change_data\" to store the\ninformation gathered by run_diff_files(). wt-status.c uses the new flag\nDIFF_OPT_DIRTY_SUBMODULES to tell diff-lib.c it wants to get detailed\ndirty information about submodules.\n\nA hint line for submodules is printed in the dirty header when dirty\nsubmodules are present.\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n---\n\n\nChanges since v2:\n- Optimized handling of separator in output\n- Added format string to color_fprintf()\n\n\n diff-lib.c                  |    6 ++++--\n diff.h                      |    1 +\n t/t7506-status-submodule.sh |    6 +++---\n wt-status.c                 |   43 ++++++++++++++++++++++++++++++++++++-------\n wt-status.h                 |    2 ++\n 5 files changed, 46 insertions(+), 12 deletions(-)\n\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 15ca7cd..63f8fcc 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -180,7 +180,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)\n \t\tchanged = ce_match_stat(ce, &st, ce_option);\n \t\tif (S_ISGITLINK(ce->ce_mode)\n \t\t    && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES)\n-\t\t    && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH))) {\n+\t\t    && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH)\n+\t\t        || DIFF_OPT_TST(&revs->diffopt, DIRTY_SUBMODULES))) {\n \t\t\tdirty_submodule = is_submodule_modified(ce->name);\n \t\t\tif (dirty_submodule)\n \t\t\t\tchanged = 1;\n@@ -243,7 +244,8 @@ static int get_stat_data(struct cache_entry *ce,\n \t\tchanged = ce_match_stat(ce, &st, 0);\n \t\tif (S_ISGITLINK(ce->ce_mode)\n \t\t    && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES)\n-\t\t    && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH))) {\n+\t\t    && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH)\n+\t\t        || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) {\n \t\t\t*dirty_submodule = is_submodule_modified(ce->name);\n \t\t\tif (*dirty_submodule)\n \t\t\t\tchanged = 1;\ndiff --git a/diff.h b/diff.h\nindex 2ef3341..95ed7f8 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -69,6 +69,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,\n #define DIFF_OPT_ALLOW_TEXTCONV      (1 << 21)\n #define DIFF_OPT_DIFF_FROM_CONTENTS  (1 << 22)\n #define DIFF_OPT_SUBMODULE_LOG       (1 << 23)\n+#define DIFF_OPT_DIRTY_SUBMODULES    (1 << 24)\n\n #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)\n #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)\ndiff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh\nindex 253c334..1c8d32a 100755\n--- a/t/t7506-status-submodule.sh\n+++ b/t/t7506-status-submodule.sh\n@@ -34,7 +34,7 @@ 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+\tgrep \"modified:   sub (new commits, modified content)\" output\n '\n\n test_expect_success 'status with modified file in submodule (porcelain)' '\n@@ -49,7 +49,7 @@ test_expect_success 'status with modified file in submodule (porcelain)' '\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+\tgrep \"modified:   sub (new commits, modified content)\" output\n '\n\n test_expect_success 'status with added file in submodule (porcelain)' '\n@@ -64,7 +64,7 @@ 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+\tgrep \"modified:   sub (new commits, untracked content)\" output\n '\n\n test_expect_success 'status with untracked file in submodule (porcelain)' '\ndiff --git a/wt-status.c b/wt-status.c\nindex 5807fc3..e0e915e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -78,7 +78,8 @@ static void wt_status_print_cached_header(struct wt_status *s)\n }\n\n static void wt_status_print_dirty_header(struct wt_status *s,\n-\t\t\t\t\t int has_deleted)\n+\t\t\t\t\t int has_deleted,\n+\t\t\t\t\t int has_dirty_submodules)\n {\n \tconst char *c = color(WT_STATUS_HEADER, s);\n\n@@ -90,6 +91,8 @@ static void wt_status_print_dirty_header(struct wt_status *s,\n \telse\n \t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add/rm <file>...\\\" to update what will be committed)\");\n \tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git checkout -- <file>...\\\" to discard changes in working directory)\");\n+\tif (has_dirty_submodules)\n+\t\tcolor_fprintf_ln(s->fp, c, \"#   (commit or discard the untracked or modified content in submodules)\");\n \tcolor_fprintf_ln(s->fp, c, \"#\");\n }\n\n@@ -144,6 +147,7 @@ static void wt_status_print_change_data(struct wt_status *s,\n \tchar *two_name;\n \tconst char *one, *two;\n \tstruct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;\n+\tstruct strbuf extra = STRBUF_INIT;\n\n \tone_name = two_name = it->string;\n \tswitch (change_type) {\n@@ -153,6 +157,17 @@ static void wt_status_print_change_data(struct wt_status *s,\n \t\t\tone_name = d->head_path;\n \t\tbreak;\n \tcase WT_STATUS_CHANGED:\n+\t\tif (d->new_submodule_commits || d->dirty_submodule) {\n+\t\t\tstrbuf_addstr(&extra, \" (\");\n+\t\t\tif (d->new_submodule_commits)\n+\t\t\t\tstrbuf_addf(&extra, \"new commits, \");\n+\t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)\n+\t\t\t\tstrbuf_addf(&extra, \"modified content, \");\n+\t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)\n+\t\t\t\tstrbuf_addf(&extra, \"untracked content, \");\n+\t\t\tstrbuf_setlen(&extra, extra.len - 2);\n+\t\t\tstrbuf_addch(&extra, ')');\n+\t\t}\n \t\tstatus = d->worktree_status;\n \t\tbreak;\n \t}\n@@ -189,6 +204,10 @@ static void wt_status_print_change_data(struct wt_status *s,\n \tdefault:\n \t\tdie(\"bug: unhandled diff status %c\", status);\n \t}\n+\tif (extra.len) {\n+\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"%s\", extra.buf);\n+\t\tstrbuf_release(&extra);\n+\t}\n \tfprintf(s->fp, \"\\n\");\n \tstrbuf_release(&onebuf);\n \tstrbuf_release(&twobuf);\n@@ -218,6 +237,9 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,\n \t\t}\n \t\tif (!d->worktree_status)\n \t\t\td->worktree_status = p->status;\n+\t\td->dirty_submodule = p->two->dirty_submodule;\n+\t\tif (S_ISGITLINK(p->two->mode))\n+\t\t\td->new_submodule_commits = !!hashcmp(p->one->sha1, p->two->sha1);\n \t}\n }\n\n@@ -281,6 +303,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)\n \tinit_revisions(&rev, NULL);\n \tsetup_revisions(0, NULL, &rev, NULL);\n \trev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;\n+\tDIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);\n \trev.diffopt.format_callback = wt_status_collect_changed_cb;\n \trev.diffopt.format_callback_data = s;\n \trev.prune_data = s->pathspec;\n@@ -418,33 +441,39 @@ static void wt_status_print_updated(struct wt_status *s)\n  *  0 : no change\n  *  1 : some change but no delete\n  */\n-static int wt_status_check_worktree_changes(struct wt_status *s)\n+static int wt_status_check_worktree_changes(struct wt_status *s,\n+\t\t\t\t\t     int *dirty_submodules)\n {\n \tint i;\n \tint changes = 0;\n\n+\t*dirty_submodules = 0;\n+\n \tfor (i = 0; i < s->change.nr; i++) {\n \t\tstruct wt_status_change_data *d;\n \t\td = s->change.items[i].util;\n \t\tif (!d->worktree_status ||\n \t\t    d->worktree_status == DIFF_STATUS_UNMERGED)\n \t\t\tcontinue;\n-\t\tchanges = 1;\n+\t\tif (!changes)\n+\t\t\tchanges = 1;\n+\t\tif (d->dirty_submodule)\n+\t\t\t*dirty_submodules = 1;\n \t\tif (d->worktree_status == DIFF_STATUS_DELETED)\n-\t\t\treturn -1;\n+\t\t\tchanges = -1;\n \t}\n \treturn changes;\n }\n\n static void wt_status_print_changed(struct wt_status *s)\n {\n-\tint i;\n-\tint worktree_changes = wt_status_check_worktree_changes(s);\n+\tint i, dirty_submodules;\n+\tint worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);\n\n \tif (!worktree_changes)\n \t\treturn;\n\n-\twt_status_print_dirty_header(s, worktree_changes < 0);\n+\twt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules);\n\n \tfor (i = 0; i < s->change.nr; i++) {\n \t\tstruct wt_status_change_data *d;\ndiff --git a/wt-status.h b/wt-status.h\nindex c60f40a..9120673 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -25,6 +25,8 @@ struct wt_status_change_data {\n \tint index_status;\n \tint stagemask;\n \tchar *head_path;\n+\tunsigned dirty_submodule       : 2;\n+\tunsigned new_submodule_commits : 1;\n };\n\n struct wt_status {\n-- \n1.7.0.1.334.g76767.dirty\n"}]}