{"thread":{"id":"54943","subject":"[PATCH] builtin/ls-files.c:add git ls-file --dedup option","startedAt":"2021-01-06T08:54:02Z","lastAt":"2021-03-19T18:12:48Z","messageCount":65,"participants":["阿德烈 via GitGitGadget","Eric Sunshine","Junio C Hamano","ZheNing Hu via GitGitGadget","胡哲宁","Johannes Schindelin","ZheNing Hu"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"413545","messageId":"pull.832.git.1609923182451.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":null,"subject":"[PATCH] builtin/ls-files.c:add git ls-file --dedup option","fromName":"阿德烈 via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-06T08:53:02Z","receivedAt":"2021-01-06T08:54:02Z","isPatch":true,"sender":{"key":"name:阿德烈","avatar":null},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\n1.When we use git ls-files with both -m -d,\nwe would find that repeated path,sometimes\nit is confusing.\n2.When we are performing a branch merge,\n the default git ls-files will also output\n multiple repeated file names.\nTherefore, I added the --dedup option to git ls-files.\n1. It can be achieved that only the deleted file name\nis displayed when using -m, -d, and --dedup at the same time.\n2. Add --dedup when merging branches to remove duplicate file\n names. (unless -s, -u are used)\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    builtin/ls-files.c:add git ls-file --dedup option\n    \n    I am reading the source code of git ls-files and learned that git ls\n    -files may have duplicate entries when conflict occurs in a branch merge\n    or when different options are used at the same time. Users may fell\n    confuse when they see these duplicate entries.\n    \n    As Junio C Hamano said ,it have odd behaviour.\n    \n    Therefore, we can provide an additional option to git ls-files to delete\n    those repeated information.\n    \n    This fixes https://github.com/gitgitgadget/git/issues/198\n    \n    Thanks!\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-832%2Fadlternative%2Fls-files-dedup-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-832/adlternative/ls-files-dedup-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/832\n\n builtin/ls-files.c | 43 ++++++++++++++++++++++++++++++++++++++-----\n 1 file changed, 38 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex c8eae899b82..66a7e251a46 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -35,6 +35,7 @@ static int line_terminator = '\\n';\n static int debug_mode;\n static int show_eol;\n static int recurse_submodules;\n+static int delete_dup;\n \n static const char *prefix;\n static int max_prefix_len;\n@@ -301,6 +302,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n {\n \tint i;\n \tstruct strbuf fullname = STRBUF_INIT;\n+\tconst struct cache_entry *last_stage=NULL;\n \n \t/* For cached/deleted files we don't need to even do the readdir */\n \tif (show_others || show_killed) {\n@@ -315,7 +317,20 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \tif (show_cached || show_stage) {\n \t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n-\n+\t\t\tif(show_cached && delete_dup){\n+\t\t\t\tswitch (ce_stage(ce)) {\n+\t\t\t\tcase 0:\n+\t\t\t\tdefault:\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 1:\n+\t\t\t\tcase 2:\n+\t\t\t\tcase 3:\n+\t\t\t\t\tif (last_stage &&\n+\t\t\t\t\t!strcmp(last_stage->name, ce->name))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tlast_stage=ce;\n+\t\t\t\t}\n+\t\t\t}\n \t\t\tconstruct_fullname(&fullname, repo, ce);\n \n \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n@@ -336,7 +351,20 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n \t\t\tstruct stat st;\n \t\t\tint err;\n-\n+\t\t\tif(delete_dup){\n+\t\t\t\tswitch (ce_stage(ce)) {\n+\t\t\t\tcase 0:\n+\t\t\t\tdefault:\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 1:\n+\t\t\t\tcase 2:\n+\t\t\t\tcase 3:\n+\t\t\t\t\tif (last_stage &&\n+\t\t\t\t\t!strcmp(last_stage->name, ce->name))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tlast_stage=ce;\n+\t\t\t\t}\n+\t\t\t}\n \t\t\tconstruct_fullname(&fullname, repo, ce);\n \n \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n@@ -347,10 +375,14 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tif (ce_skip_worktree(ce))\n \t\t\t\tcontinue;\n \t\t\terr = lstat(fullname.buf, &st);\n-\t\t\tif (show_deleted && err)\n+\t\t\tif(delete_dup && show_deleted && show_modified && err)\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\telse{\n+\t\t\t\tif (show_deleted && err)/* you can't find it,so it's actually removed at all! */\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\t}\n \t\t}\n \t}\n \n@@ -578,6 +610,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n+\t\tOPT_BOOL(0, \"dedup\", &delete_dup, N_(\"delete duplicate entry in index\")),\n \t\tOPT_END()\n \t};\n \n\nbase-commit: 6d3ef5b467eccd2769f1aa1c555d317d3c8dc707\n-- \ngitgitgadget\n"},{"id":"413644","messageId":"CAPig+cTY0qGkYZ_OjvFUhopoioCPzaWEk=AX1mNgcOsF60hwAg@mail.gmail.com","threadId":"54943","inReplyTo":"pull.832.git.1609923182451.gitgitgadget@gmail.com","subject":"Re: [PATCH] builtin/ls-files.c:add git ls-file --dedup option","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-01-07T06:10:22Z","receivedAt":"2021-01-07T06:11:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Jan 6, 2021 at 3:54 AM 阿德烈 via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> [...]\n> Therefore, I added the --dedup option to git ls-files.\n> 1. It can be achieved that only the deleted file name\n> is displayed when using -m, -d, and --dedup at the same time.\n> 2. Add --dedup when merging branches to remove duplicate file\n>  names. (unless -s, -u are used)\n\nI'm just pointing out a few minor style issues below; I'm not properly\nreviewing the patch...\n\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  builtin/ls-files.c | 43 ++++++++++++++++++++++++++++++++++++++-----\n> 1 file changed, 38 insertions(+), 5 deletions(-)\n\nThis change adds a new command-line option, so the documentation\n(Documentation/git-ls-files.txt) should be updated and at least one\nnew test should be added (in one of the t/t30??-ls-files-*.sh scripts\nprobably).\n\n> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> @@ -301,6 +302,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>         struct strbuf fullname = STRBUF_INIT;\n> +       const struct cache_entry *last_stage=NULL;\n\nAdd spaces around `=` similar to the preceding line:\n\n    const struct cache_entry *last_stage = NULL;\n\n> @@ -315,7 +317,20 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>                 for (i = 0; i < repo->index->cache_nr; i++) {\n>                         const struct cache_entry *ce = repo->index->cache[i];\n> -\n\nThis patch deletes the blank line but this project usually prefers to\nhave a blank line after declarations.\n\n> +                       if(show_cached && delete_dup){\n\nAdd space after `if` and before `{`:\n\n    if (show_cached && delete_dup) {\n\n> @@ -336,7 +351,20 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n> +                       if(delete_dup){\n\nStyle: if (delete_dup) {\n\n> @@ -347,10 +375,14 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n> -                       if (show_deleted && err)\n> +                       if(delete_dup && show_deleted && show_modified && err)\n\nStyle: if (delete_dup && ...\n\n> -                       if (show_modified && ie_modified(repo->index, ce, &st, 0))\n> -                               show_ce(repo, dir, ce, fullname.buf, tag_modified);\n> +                       else{\n\nStyle: else {\n\n> +                               if (show_deleted && err)/* you can't find it,so it's actually removed at all! */\n\nAdd space before `/* comment */`.\nAdd space in \"...it, so...\".\n\n> @@ -578,6 +610,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n> +               OPT_BOOL(0, \"dedup\", &delete_dup, N_(\"delete duplicate entry in index\")),\n\nThe short help makes it seem like it's modifying the index. Perhaps instead:\n\n    N_(\"suppress duplicate entries\")\n"},{"id":"413656","messageId":"xmqqturtmeql.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"CAPig+cTY0qGkYZ_OjvFUhopoioCPzaWEk=AX1mNgcOsF60hwAg@mail.gmail.com","subject":"Re: [PATCH] builtin/ls-files.c:add git ls-file --dedup option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-07T06:40:34Z","receivedAt":"2021-01-07T06:41:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n>> +                               if (show_deleted && err)/* you can't find it,so it's actually removed at all! */\n>\n> Add space before `/* comment */`.\n> Add space in \"...it, so...\".\n\nAvoid overly long lines.\n"},{"id":"413816","messageId":"pull.832.v2.git.1610116600.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.git.1609923182451.gitgitgadget@gmail.com","subject":"[PATCH v2 0/2] builtin/ls-files.c:add git ls-file --dedup option","fromName":"阿德烈 via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-08T14:36:37Z","receivedAt":"2021-01-08T14:38:04Z","isPatch":true,"sender":{"key":"name:阿德烈","avatar":null},"body":"I am reading the source code of git ls-files and learned that git ls -files\nmay have duplicate entries when conflict occurs in a branch merge or when\ndifferent options are used at the same time. Users may fell confuse when\nthey see these duplicate entries.\n\nAs Junio C Hamano said ,it have odd behaviour.\n\nTherefore, we can provide an additional option to git ls-files to delete\nthose repeated information.\n\nThis fixes https://github.com/gitgitgadget/git/issues/198\n\nThanks!\n\nZheNing Hu (2):\n  builtin/ls-files.c:add git ls-file --dedup option\n  builtin:ls-files.c:add git ls-file --dedup option\n\n Documentation/git-ls-files.txt |  4 +++\n builtin/ls-files.c             | 41 ++++++++++++++++++++--\n t/t3012-ls-files-dedup.sh      | 63 ++++++++++++++++++++++++++++++++++\n 3 files changed, 105 insertions(+), 3 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\n\nbase-commit: 6d3ef5b467eccd2769f1aa1c555d317d3c8dc707\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-832%2Fadlternative%2Fls-files-dedup-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-832/adlternative/ls-files-dedup-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/832\n\nRange-diff vs v1:\n\n 1:  0261e5d245e = 1:  0261e5d245e builtin/ls-files.c:add git ls-file --dedup option\n -:  ----------- > 2:  a09a5098aa6 builtin:ls-files.c:add git ls-file --dedup option\n\n-- \ngitgitgadget\n"},{"id":"413817","messageId":"0261e5d245ef0a5b9a717be1bc03492d7bc06c5e.1610116600.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v2.git.1610116600.gitgitgadget@gmail.com","subject":"[PATCH v2 1/2] builtin/ls-files.c:add git ls-file --dedup option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-08T14:36:38Z","receivedAt":"2021-01-08T14:38:04Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\n1.When we use git ls-files with both -m -d,\nwe would find that repeated path,sometimes\nit is confusing.\n2.When we are performing a branch merge,\n the default git ls-files will also output\n multiple repeated file names.\nTherefore, I added the --dedup option to git ls-files.\n1. It can be achieved that only the deleted file name\nis displayed when using -m, -d, and --dedup at the same time.\n2. Add --dedup when merging branches to remove duplicate file\n names. (unless -s, -u are used)\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n builtin/ls-files.c | 43 ++++++++++++++++++++++++++++++++++++++-----\n 1 file changed, 38 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex c8eae899b82..66a7e251a46 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -35,6 +35,7 @@ static int line_terminator = '\\n';\n static int debug_mode;\n static int show_eol;\n static int recurse_submodules;\n+static int delete_dup;\n \n static const char *prefix;\n static int max_prefix_len;\n@@ -301,6 +302,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n {\n \tint i;\n \tstruct strbuf fullname = STRBUF_INIT;\n+\tconst struct cache_entry *last_stage=NULL;\n \n \t/* For cached/deleted files we don't need to even do the readdir */\n \tif (show_others || show_killed) {\n@@ -315,7 +317,20 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \tif (show_cached || show_stage) {\n \t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n-\n+\t\t\tif(show_cached && delete_dup){\n+\t\t\t\tswitch (ce_stage(ce)) {\n+\t\t\t\tcase 0:\n+\t\t\t\tdefault:\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 1:\n+\t\t\t\tcase 2:\n+\t\t\t\tcase 3:\n+\t\t\t\t\tif (last_stage &&\n+\t\t\t\t\t!strcmp(last_stage->name, ce->name))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tlast_stage=ce;\n+\t\t\t\t}\n+\t\t\t}\n \t\t\tconstruct_fullname(&fullname, repo, ce);\n \n \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n@@ -336,7 +351,20 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n \t\t\tstruct stat st;\n \t\t\tint err;\n-\n+\t\t\tif(delete_dup){\n+\t\t\t\tswitch (ce_stage(ce)) {\n+\t\t\t\tcase 0:\n+\t\t\t\tdefault:\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 1:\n+\t\t\t\tcase 2:\n+\t\t\t\tcase 3:\n+\t\t\t\t\tif (last_stage &&\n+\t\t\t\t\t!strcmp(last_stage->name, ce->name))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tlast_stage=ce;\n+\t\t\t\t}\n+\t\t\t}\n \t\t\tconstruct_fullname(&fullname, repo, ce);\n \n \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n@@ -347,10 +375,14 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tif (ce_skip_worktree(ce))\n \t\t\t\tcontinue;\n \t\t\terr = lstat(fullname.buf, &st);\n-\t\t\tif (show_deleted && err)\n+\t\t\tif(delete_dup && show_deleted && show_modified && err)\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\telse{\n+\t\t\t\tif (show_deleted && err)/* you can't find it,so it's actually removed at all! */\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\t}\n \t\t}\n \t}\n \n@@ -578,6 +610,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n+\t\tOPT_BOOL(0, \"dedup\", &delete_dup, N_(\"delete duplicate entry in index\")),\n \t\tOPT_END()\n \t};\n \n-- \ngitgitgadget\n\n"},{"id":"413818","messageId":"a09a5098aa66ea0ed89fe0fcde3f016b4a65814d.1610116600.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v2.git.1610116600.gitgitgadget@gmail.com","subject":"[PATCH v2 2/2] builtin:ls-files.c:add git ls-file --dedup option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-08T14:36:39Z","receivedAt":"2021-01-08T14:38:04Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThis commit standardizes the code format.\nFor git ls-file --dedup option added\nrelevant descriptions in Documentation/git-ls-files.txt\nand wrote t/t3012-ls-files-dedup.sh test script\nto prove the correctness of--dedup option.\n\nthis patch fixed: https://github.com/gitgitgadget/git/issues/198\nThanks.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-ls-files.txt |  4 +++\n builtin/ls-files.c             | 20 ++++++-----\n t/t3012-ls-files-dedup.sh      | 63 ++++++++++++++++++++++++++++++++++\n 3 files changed, 78 insertions(+), 9 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\ndiff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\nindex cbcf5263dd0..41a9c5a8b27 100644\n--- a/Documentation/git-ls-files.txt\n+++ b/Documentation/git-ls-files.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t\t(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*\n \t\t(-[c|d|o|i|s|u|k|m])*\n \t\t[--eol]\n+\t\t[--dedup]\n \t\t[-x <pattern>|--exclude=<pattern>]\n \t\t[-X <file>|--exclude-from=<file>]\n \t\t[--exclude-per-directory=<file>]\n@@ -81,6 +82,9 @@ OPTIONS\n \t\\0 line termination on output and do not quote filenames.\n \tSee OUTPUT below for more information.\n \n+--dedup::\n+\tSuppress duplicates entries when conflicts happen or\n+\tspecify -d -m at the same time.\n -x <pattern>::\n --exclude=<pattern>::\n \tSkip untracked files matching pattern.\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 66a7e251a46..bc4eded19ab 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -302,7 +302,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n {\n \tint i;\n \tstruct strbuf fullname = STRBUF_INIT;\n-\tconst struct cache_entry *last_stage=NULL;\n+\tconst struct cache_entry *last_stage = NULL;\n \n \t/* For cached/deleted files we don't need to even do the readdir */\n \tif (show_others || show_killed) {\n@@ -317,7 +317,8 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \tif (show_cached || show_stage) {\n \t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n-\t\t\tif(show_cached && delete_dup){\n+\n+\t\t\tif (show_cached && delete_dup) {\n \t\t\t\tswitch (ce_stage(ce)) {\n \t\t\t\tcase 0:\n \t\t\t\tdefault:\n@@ -328,7 +329,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\t\t\tif (last_stage &&\n \t\t\t\t\t!strcmp(last_stage->name, ce->name))\n \t\t\t\t\t\tcontinue;\n-\t\t\t\t\tlast_stage=ce;\n+\t\t\t\t\tlast_stage = ce;\n \t\t\t\t}\n \t\t\t}\n \t\t\tconstruct_fullname(&fullname, repo, ce);\n@@ -351,7 +352,8 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n \t\t\tstruct stat st;\n \t\t\tint err;\n-\t\t\tif(delete_dup){\n+\n+\t\t\tif (delete_dup) {\n \t\t\t\tswitch (ce_stage(ce)) {\n \t\t\t\tcase 0:\n \t\t\t\tdefault:\n@@ -362,7 +364,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\t\t\tif (last_stage &&\n \t\t\t\t\t!strcmp(last_stage->name, ce->name))\n \t\t\t\t\t\tcontinue;\n-\t\t\t\t\tlast_stage=ce;\n+\t\t\t\t\tlast_stage = ce;\n \t\t\t\t}\n \t\t\t}\n \t\t\tconstruct_fullname(&fullname, repo, ce);\n@@ -375,10 +377,10 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tif (ce_skip_worktree(ce))\n \t\t\t\tcontinue;\n \t\t\terr = lstat(fullname.buf, &st);\n-\t\t\tif(delete_dup && show_deleted && show_modified && err)\n+\t\t\tif (delete_dup && show_deleted && show_modified && err)\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\telse{\n-\t\t\t\tif (show_deleted && err)/* you can't find it,so it's actually removed at all! */\n+\t\t\telse {\n+\t\t\t\tif (show_deleted && err)\n \t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n \t\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n \t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n@@ -610,7 +612,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n-\t\tOPT_BOOL(0, \"dedup\", &delete_dup, N_(\"delete duplicate entry in index\")),\n+\t\tOPT_BOOL(0, \"dedup\", &delete_dup, N_(\"suppress duplicate entries\")),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\nnew file mode 100755\nindex 00000000000..00c7f65cfc1\n--- /dev/null\n+++ b/t/t3012-ls-files-dedup.sh\n@@ -0,0 +1,63 @@\n+#!/bin/sh\n+\n+test_description='git ls-files --dedup test.\n+\n+This test prepares the following in the cache:\n+\n+    a.txt       - a file(base)\n+    a.txt\t- a file(master)\n+    a.txt       - a file(dev)\n+    b.txt       - a file\n+    delete.txt  - a file\n+    expect1\t- a file\n+    expect2\t- a file\n+\n+'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'master branch setup and write expect1 expect2 and commit' '\n+\ttouch a.txt &&\n+\ttouch b.txt &&\n+\ttouch delete.txt &&\n+\tcat <<-EOF >expect1 &&\n+\tM a.txt\n+\tH b.txt\n+\tH delete.txt\n+\tH expect1\n+\tH expect2\n+\tEOF\n+\tcat <<-EOF >expect2 &&\n+\tC a.txt\n+\tR delete.txt\n+\tEOF\n+\tgit add a.txt b.txt delete.txt expect1 expect2 &&\n+\tgit commit -m master:1\n+'\n+\n+test_expect_success 'main commit again' '\n+\techo a>a.txt &&\n+\techo b>b.txt &&\n+\techo delete>delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m master:2\n+'\n+\n+test_expect_success 'dev commit' '\n+\tgit checkout HEAD~ &&\n+\tgit switch -c dev &&\n+\techo change>a.txt &&\n+\tgit add a.txt &&\n+\tgit commit -m dev:1\n+'\n+\n+test_expect_success 'dev merge master' '\n+\ttest_must_fail git merge master &&\n+\tgit ls-files -t --dedup >actual1 &&\n+\ttest_cmp expect1 actual1 &&\n+\trm delete.txt &&\n+\tgit ls-files -d -m -t --dedup >actual2 &&\n+\ttest_cmp expect2 actual2\n+'\n+\n+test_done\n-- \ngitgitgadget\n"},{"id":"414328","messageId":"CAPig+cT+1XZbg3Nv15Hfsmc0qLTku0iMvp0dOjuebv_55D1OSQ@mail.gmail.com","threadId":"54943","inReplyTo":"a09a5098aa66ea0ed89fe0fcde3f016b4a65814d.1610116600.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/2] builtin:ls-files.c:add git ls-file --dedup option","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-01-14T06:38:58Z","receivedAt":"2021-01-14T06:40:08Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Jan 8, 2021 at 9:36 AM ZheNing Hu via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> builtin:ls-files.c:add git ls-file --dedup option\n\nThis subject concisely explains the purpose of the patch. That's good.\nA more typical way to write it would be:\n\n    ls-files: add --dedup option\n\n> This commit standardizes the code format.\n\nFixing problems pointed out by reviewers is good. Normally, however,\nwhen you submit a new version of your patch or patch series, you\nshould apply these fixes directly to the patch(es) which introduced\nthe problems in the first place rather than adding one or more\nadditional patches to fix problems introduced in earlier patches. To\ndo this, you typically would use `git rebase -i` or `git commit\n--amend` to squash the fixes into the problematic patches. Thus, when\nyou re-submit the patches, they will appear to be \"perfect\".\n\nFor this particular two-patch series, patch [2/2] is doing two things:\n(1) fixing style problems from patch [1/2], and (2) adding\ndocumentation and tests which logically belong with the feature added\nby patch [1/2]. Taking the above advice into account, a better\npresentation when you re-submit this series would be to squash these\ntwo patches into a single patch.\n\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n> diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\n> @@ -81,6 +82,9 @@ OPTIONS\n> +--dedup::\n> +       Suppress duplicates entries when conflicts happen or\n> +       specify -d -m at the same time.\n\nFor consistency with typesetting elsewhere in this file, use backticks\naround the command-line options. It also often is a good idea to spell\nthe options using long form since it is typically easier to search for\nthe long form of an option in documentation. So, perhaps the above can\nbe written like this:\n\n    Suppress duplicate entries when `--deleted` and `--modified` are\n    combined.\n\n> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> -       const struct cache_entry *last_stage=NULL;\n> +       const struct cache_entry *last_stage = NULL;\n> -                       if(show_cached && delete_dup){\n> +                       if (show_cached && delete_dup) {\n> -                                       last_stage=ce;\n> +                                       last_stage = ce;\n> -                       if(delete_dup){\n> +                       if (delete_dup) {\n> -                       if(delete_dup && show_deleted && show_modified && err)\n> +                       if (delete_dup && show_deleted && show_modified && err)\n> -                       else{\n> -                               if (show_deleted && err)/* you can't find it,so it's actually removed at all! */\n> +                       else {\n> +                               if (show_deleted && err)\n\nAs mentioned above, these style fixes should be squashed into the\nfirst patch, rather than being done in a separate patch, so that\nreviewers see a nicely polished patch rather than a patch which\nrequires later fixing up.\n\n> diff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\n> @@ -0,0 +1,63 @@\n> +test_expect_success 'master branch setup and write expect1 expect2 and commit' '\n\nWe usually give this test a simple title such as \"setup\" so that we\ndon't have to worry about the title becoming outdated as people make\nchanges to the test itself.\n\n> +       touch a.txt &&\n> +       touch b.txt &&\n> +       touch delete.txt &&\n\nOn this project, we use `touch` when the timestamp of the empty files\nis important to the test. If the timestamp is not important, then we\njust use `>`, like this:\n\n    >a.txt &&\n    >b.txt &&\n    >delete.txt &&\n\n> +       cat <<-EOF >expect1 &&\n> +       M a.txt\n> +       H b.txt\n> +       H delete.txt\n> +       H expect1\n> +       H expect2\n> +       EOF\n> +       cat <<-EOF >expect2 &&\n> +       C a.txt\n> +       R delete.txt\n> +       EOF\n\nWhen no variables are being interpolated in the here-doc content, we\nuse -\\EOF to let readers know that the here-doc body is literal. So:\n\n    cat >expect1 <<-\\EOF &&\n    ...\n    EOF\n\n> +       git add a.txt b.txt delete.txt expect1 expect2 &&\n> +       git commit -m master:1\n> +'\n> +\n> +test_expect_success 'main commit again' '\n> +       echo a>a.txt &&\n> +       echo b>b.txt &&\n> +       echo delete>delete.txt &&\n> +       git add a.txt b.txt delete.txt &&\n> +       git commit -m master:2\n> +'\n> +\n> +test_expect_success 'dev commit' '\n> +       git checkout HEAD~ &&\n> +       git switch -c dev &&\n> +       echo change>a.txt &&\n> +       git add a.txt &&\n> +       git commit -m dev:1\n> +'\n\nThese two tests following the \"setup\" test also seem to be doing setup\ntasks rather than testing the new --dedup functionality. If this is\nthe case, then it probably would make sense to combine all three tests\ninto a single \"setup\" test.\n\n> +test_expect_success 'dev merge master' '\n> +       test_must_fail git merge master &&\n> +       git ls-files -t --dedup >actual1 &&\n> +       test_cmp expect1 actual1 &&\n> +       rm delete.txt &&\n> +       git ls-files -d -m -t --dedup >actual2 &&\n> +       test_cmp expect2 actual2\n> +'\n\nDo you foresee that people will add more tests to this file which will\nuse the files and branches set up by the \"setup\" test(s)? If not, if\nthose branches and files are only ever going to be used by this one\ntest, then it probably would be better to combine all the above code\ninto a single test.\n"},{"id":"414340","messageId":"CAOLTT8SPMA5hNF2GrbjWzQusjq1tCz1SC2842ApcYAVE9tYHew@mail.gmail.com","threadId":"54943","inReplyTo":"CAPig+cT+1XZbg3Nv15Hfsmc0qLTku0iMvp0dOjuebv_55D1OSQ@mail.gmail.com","subject":"Re: [PATCH v2 2/2] builtin:ls-files.c:add git ls-file --dedup option","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-14T08:17:21Z","receivedAt":"2021-01-14T08:16:39Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"You can see that the coding and documentation of GIT community are really very\nstandard, which may be one of the things I lack and need to improve ;)\nThanks for patiently correct my errors.\n\nEric Sunshine <sunshine@sunshineco.com> 于2021年1月14日周四 下午2:39写道：\n>\n> On Fri, Jan 8, 2021 at 9:36 AM ZheNing Hu via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > builtin:ls-files.c:add git ls-file --dedup option\n>\n> This subject concisely explains the purpose of the patch. That's good.\n> A more typical way to write it would be:\n>\n>     ls-files: add --dedup option\n>\nOK.I will correct it more specification.\n> > This commit standardizes the code format.\n>\n> Fixing problems pointed out by reviewers is good. Normally, however,\n> when you submit a new version of your patch or patch series, you\n> should apply these fixes directly to the patch(es) which introduced\n> the problems in the first place rather than adding one or more\n> additional patches to fix problems introduced in earlier patches. To\n> do this, you typically would use `git rebase -i` or `git commit\n> --amend` to squash the fixes into the problematic patches. Thus, when\n> you re-submit the patches, they will appear to be \"perfect\".\n>\n> For this particular two-patch series, patch [2/2] is doing two things:\n> (1) fixing style problems from patch [1/2], and (2) adding\n> documentation and tests which logically belong with the feature added\n> by patch [1/2]. Taking the above advice into account, a better\n> presentation when you re-submit this series would be to squash these\n> two patches into a single patch.\n>\nI thought before this was gitgitgadget would sent duplicate patch\nover and over again. It seems like I really should go straight ahead\nand squash my commits , so I know what I should do.\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > ---\n> > diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\n> > @@ -81,6 +82,9 @@ OPTIONS\n> > +--dedup::\n> > +       Suppress duplicates entries when conflicts happen or\n> > +       specify -d -m at the same time.\n>\n> For consistency with typesetting elsewhere in this file, use backticks\n> around the command-line options. It also often is a good idea to spell\n> the options using long form since it is typically easier to search for\n> the long form of an option in documentation. So, perhaps the above can\n> be written like this:\n>\n>     Suppress duplicate entries when `--deleted` and `--modified` are\n>     combined.\n>\n> > diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> > -       const struct cache_entry *last_stage=NULL;\n> > +       const struct cache_entry *last_stage = NULL;\n> > -                       if(show_cached && delete_dup){\n> > +                       if (show_cached && delete_dup) {\n> > -                                       last_stage=ce;\n> > +                                       last_stage = ce;\n> > -                       if(delete_dup){\n> > +                       if (delete_dup) {\n> > -                       if(delete_dup && show_deleted && show_modified && err)\n> > +                       if (delete_dup && show_deleted && show_modified && err)\n> > -                       else{\n> > -                               if (show_deleted && err)/* you can't find it,so it's actually removed at all! */\n> > +                       else {\n> > +                               if (show_deleted && err)\n>\n> As mentioned above, these style fixes should be squashed into the\n> first patch, rather than being done in a separate patch, so that\n> reviewers see a nicely polished patch rather than a patch which\n> requires later fixing up.\n>\n> > diff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\n> > @@ -0,0 +1,63 @@\n> > +test_expect_success 'master branch setup and write expect1 expect2 and commit' '\n>\n> We usually give this test a simple title such as \"setup\" so that we\n> don't have to worry about the title becoming outdated as people make\n> changes to the test itself.\n>\n> > +       touch a.txt &&\n> > +       touch b.txt &&\n> > +       touch delete.txt &&\n>\n> On this project, we use `touch` when the timestamp of the empty files\n> is important to the test. If the timestamp is not important, then we\n> just use `>`, like this:\n>\n>     >a.txt &&\n>     >b.txt &&\n>     >delete.txt &&\n>\nOK,maybe because I always use touch to generate files.\n> > +       cat <<-EOF >expect1 &&\n> > +       M a.txt\n> > +       H b.txt\n> > +       H delete.txt\n> > +       H expect1\n> > +       H expect2\n> > +       EOF\n> > +       cat <<-EOF >expect2 &&\n> > +       C a.txt\n> > +       R delete.txt\n> > +       EOF\n>\n> When no variables are being interpolated in the here-doc content, we\n> use -\\EOF to let readers know that the here-doc body is literal. So:\n>\n>     cat >expect1 <<-\\EOF &&\n>     ...\n>     EOF\n>\n> > +       git add a.txt b.txt delete.txt expect1 expect2 &&\n> > +       git commit -m master:1\n> > +'\n> > +\n> > +test_expect_success 'main commit again' '\n> > +       echo a>a.txt &&\n> > +       echo b>b.txt &&\n> > +       echo delete>delete.txt &&\n> > +       git add a.txt b.txt delete.txt &&\n> > +       git commit -m master:2\n> > +'\n> > +\n> > +test_expect_success 'dev commit' '\n> > +       git checkout HEAD~ &&\n> > +       git switch -c dev &&\n> > +       echo change>a.txt &&\n> > +       git add a.txt &&\n> > +       git commit -m dev:1\n> > +'\n>\n> These two tests following the \"setup\" test also seem to be doing setup\n> tasks rather than testing the new --dedup functionality. If this is\n> the case, then it probably would make sense to combine all three tests\n> into a single \"setup\" test.\n>\n> > +test_expect_success 'dev merge master' '\n> > +       test_must_fail git merge master &&\n> > +       git ls-files -t --dedup >actual1 &&\n> > +       test_cmp expect1 actual1 &&\n> > +       rm delete.txt &&\n> > +       git ls-files -d -m -t --dedup >actual2 &&\n> > +       test_cmp expect2 actual2\n> > +'\n>\n> Do you foresee that people will add more tests to this file which will\n> use the files and branches set up by the \"setup\" test(s)? If not, if\n> those branches and files are only ever going to be used by this one\n> test, then it probably would be better to combine all the above code\n> into a single test.\nNo,the test file may just need only one.\n"},{"id":"414354","messageId":"pull.832.v3.git.1610626942677.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v2.git.1610116600.gitgitgadget@gmail.com","subject":"[PATCH v3] ls-files.c: add --dedup option","fromName":"阿德烈 via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-14T12:22:22Z","receivedAt":"2021-01-14T12:23:08Z","isPatch":true,"sender":{"key":"name:阿德烈","avatar":null},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nIn order to provide users a better experience\nwhen viewing information about files in the index\nand the working tree, the `--dedup` option will suppress\nsome duplicate options under some conditions.\n\nIn a merge conflict, one item of \"git ls-files\" output may\nappear multiple times. For example,now the file `a.c` has\na conflict,`a.c` will appear three times in the output of\n\"git ls-files\".We can use \"git ls-files --dedup\" to output\n`a.c` only one time.(unless `--stage` or `--unmerged` is\nused to view all the detailed information in the index)\n\nIn addition, if you use both `--delete` and `--modify` in\nthe same time, The `--dedup` option can also suppress modified\nentries output.\n\n`--dedup` option relevant descriptions in\n`Documentation/git-ls-files.txt`,\nthe test script in `t/t3012-ls-files-dedup.sh`\nprove the correctness of the `--dedup` option.\n\nthis patch fixed:\nhttps://github.com/gitgitgadget/git/issues/198\nThanks.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    builtin/ls-files.c:add git ls-file --dedup option\n    \n    I am reading the source code of git ls-files and learned that git ls\n    -files may have duplicate entries when conflict occurs in a branch merge\n    or when different options are used at the same time. Users may fell\n    confuse when they see these duplicate entries.\n    \n    As Junio C Hamano said ,it have odd behaviour.\n    \n    Therefore, we can provide an additional option to git ls-files to delete\n    those repeated information.\n    \n    This fixes https://github.com/gitgitgadget/git/issues/198\n    \n    Thanks!\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-832%2Fadlternative%2Fls-files-dedup-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-832/adlternative/ls-files-dedup-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/832\n\nRange-diff vs v2:\n\n 1:  0261e5d245e < -:  ----------- builtin/ls-files.c:add git ls-file --dedup option\n 2:  a09a5098aa6 ! 1:  5ce52c8b7a4 builtin:ls-files.c:add git ls-file --dedup option\n     @@ Metadata\n      Author: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Commit message ##\n     -    builtin:ls-files.c:add git ls-file --dedup option\n     +    ls-files.c: add --dedup option\n      \n     -    This commit standardizes the code format.\n     -    For git ls-file --dedup option added\n     -    relevant descriptions in Documentation/git-ls-files.txt\n     -    and wrote t/t3012-ls-files-dedup.sh test script\n     -    to prove the correctness of--dedup option.\n     +    In order to provide users a better experience\n     +    when viewing information about files in the index\n     +    and the working tree, the `--dedup` option will suppress\n     +    some duplicate options under some conditions.\n      \n     -    this patch fixed: https://github.com/gitgitgadget/git/issues/198\n     +    In a merge conflict, one item of \"git ls-files\" output may\n     +    appear multiple times. For example,now the file `a.c` has\n     +    a conflict,`a.c` will appear three times in the output of\n     +    \"git ls-files\".We can use \"git ls-files --dedup\" to output\n     +    `a.c` only one time.(unless `--stage` or `--unmerged` is\n     +    used to view all the detailed information in the index)\n     +\n     +    In addition, if you use both `--delete` and `--modify` in\n     +    the same time, The `--dedup` option can also suppress modified\n     +    entries output.\n     +\n     +    `--dedup` option relevant descriptions in\n     +    `Documentation/git-ls-files.txt`,\n     +    the test script in `t/t3012-ls-files-dedup.sh`\n     +    prove the correctness of the `--dedup` option.\n     +\n     +    this patch fixed:\n     +    https://github.com/gitgitgadget/git/issues/198\n          Thanks.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n     @@ Documentation/git-ls-files.txt: OPTIONS\n       \tSee OUTPUT below for more information.\n       \n      +--dedup::\n     -+\tSuppress duplicates entries when conflicts happen or\n     -+\tspecify -d -m at the same time.\n     ++\tSuppress duplicate entries when conflict happen or `--deleted`\n     ++\tand `--modified` are combined.\n     ++\n       -x <pattern>::\n       --exclude=<pattern>::\n       \tSkip untracked files matching pattern.\n      \n       ## builtin/ls-files.c ##\n     +@@ builtin/ls-files.c: static int line_terminator = '\\n';\n     + static int debug_mode;\n     + static int show_eol;\n     + static int recurse_submodules;\n     ++static int delete_dup;\n     + \n     + static const char *prefix;\n     + static int max_prefix_len;\n      @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n       {\n       \tint i;\n       \tstruct strbuf fullname = STRBUF_INIT;\n     --\tconst struct cache_entry *last_stage=NULL;\n      +\tconst struct cache_entry *last_stage = NULL;\n       \n       \t/* For cached/deleted files we don't need to even do the readdir */\n       \tif (show_others || show_killed) {\n      @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     - \tif (show_cached || show_stage) {\n       \t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n       \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n     --\t\t\tif(show_cached && delete_dup){\n     -+\n     + \n      +\t\t\tif (show_cached && delete_dup) {\n     - \t\t\t\tswitch (ce_stage(ce)) {\n     - \t\t\t\tcase 0:\n     - \t\t\t\tdefault:\n     -@@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     - \t\t\t\t\tif (last_stage &&\n     - \t\t\t\t\t!strcmp(last_stage->name, ce->name))\n     - \t\t\t\t\t\tcontinue;\n     --\t\t\t\t\tlast_stage=ce;\n     ++\t\t\t\tswitch (ce_stage(ce)) {\n     ++\t\t\t\tcase 0:\n     ++\t\t\t\tdefault:\n     ++\t\t\t\t\tbreak;\n     ++\t\t\t\tcase 1:\n     ++\t\t\t\tcase 2:\n     ++\t\t\t\tcase 3:\n     ++\t\t\t\t\tif (last_stage &&\n     ++\t\t\t\t\t!strcmp(last_stage->name, ce->name))\n     ++\t\t\t\t\t\tcontinue;\n      +\t\t\t\t\tlast_stage = ce;\n     - \t\t\t\t}\n     - \t\t\t}\n     ++\t\t\t\t}\n     ++\t\t\t}\n       \t\t\tconstruct_fullname(&fullname, repo, ce);\n     + \n     + \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n      @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     - \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n       \t\t\tstruct stat st;\n       \t\t\tint err;\n     --\t\t\tif(delete_dup){\n     -+\n     + \n      +\t\t\tif (delete_dup) {\n     - \t\t\t\tswitch (ce_stage(ce)) {\n     - \t\t\t\tcase 0:\n     - \t\t\t\tdefault:\n     -@@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     - \t\t\t\t\tif (last_stage &&\n     - \t\t\t\t\t!strcmp(last_stage->name, ce->name))\n     - \t\t\t\t\t\tcontinue;\n     --\t\t\t\t\tlast_stage=ce;\n     ++\t\t\t\tswitch (ce_stage(ce)) {\n     ++\t\t\t\tcase 0:\n     ++\t\t\t\tdefault:\n     ++\t\t\t\t\tbreak;\n     ++\t\t\t\tcase 1:\n     ++\t\t\t\tcase 2:\n     ++\t\t\t\tcase 3:\n     ++\t\t\t\t\tif (last_stage &&\n     ++\t\t\t\t\t!strcmp(last_stage->name, ce->name))\n     ++\t\t\t\t\t\tcontinue;\n      +\t\t\t\t\tlast_stage = ce;\n     - \t\t\t\t}\n     - \t\t\t}\n     ++\t\t\t\t}\n     ++\t\t\t}\n       \t\t\tconstruct_fullname(&fullname, repo, ce);\n     + \n     + \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n      @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n       \t\t\tif (ce_skip_worktree(ce))\n       \t\t\t\tcontinue;\n       \t\t\terr = lstat(fullname.buf, &st);\n     --\t\t\tif(delete_dup && show_deleted && show_modified && err)\n     +-\t\t\tif (show_deleted && err)\n      +\t\t\tif (delete_dup && show_deleted && show_modified && err)\n       \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     --\t\t\telse{\n     --\t\t\t\tif (show_deleted && err)/* you can't find it,so it's actually removed at all! */\n     +-\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n     +-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n      +\t\t\telse {\n      +\t\t\t\tif (show_deleted && err)\n     - \t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     - \t\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n     - \t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     ++\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     ++\t\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n     ++\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     ++\t\t\t}\n     + \t\t}\n     + \t}\n     + \n      @@ builtin/ls-files.c: int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n       \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n       \t\tOPT__ABBREV(&abbrev),\n       \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n     --\t\tOPT_BOOL(0, \"dedup\", &delete_dup, N_(\"delete duplicate entry in index\")),\n      +\t\tOPT_BOOL(0, \"dedup\", &delete_dup, N_(\"suppress duplicate entries\")),\n       \t\tOPT_END()\n       \t};\n     @@ t/t3012-ls-files-dedup.sh (new)\n      +\n      +. ./test-lib.sh\n      +\n     -+test_expect_success 'master branch setup and write expect1 expect2 and commit' '\n     -+\ttouch a.txt &&\n     -+\ttouch b.txt &&\n     -+\ttouch delete.txt &&\n     -+\tcat <<-EOF >expect1 &&\n     ++test_expect_success 'setup' '\n     ++\t> a.txt &&\n     ++\t> b.txt &&\n     ++\t> delete.txt &&\n     ++\tcat >expect1<<-\\EOF &&\n      +\tM a.txt\n      +\tH b.txt\n      +\tH delete.txt\n      +\tH expect1\n      +\tH expect2\n      +\tEOF\n     -+\tcat <<-EOF >expect2 &&\n     ++\tcat >expect2<<-EOF &&\n      +\tC a.txt\n      +\tR delete.txt\n      +\tEOF\n      +\tgit add a.txt b.txt delete.txt expect1 expect2 &&\n     -+\tgit commit -m master:1\n     -+'\n     -+\n     -+test_expect_success 'main commit again' '\n     ++\tgit commit -m master:1 &&\n      +\techo a>a.txt &&\n      +\techo b>b.txt &&\n     -+\techo delete>delete.txt &&\n     ++\techo delete >delete.txt &&\n      +\tgit add a.txt b.txt delete.txt &&\n     -+\tgit commit -m master:2\n     -+'\n     -+\n     -+test_expect_success 'dev commit' '\n     ++\tgit commit -m master:2 &&\n      +\tgit checkout HEAD~ &&\n      +\tgit switch -c dev &&\n     -+\techo change>a.txt &&\n     ++\techo change >a.txt &&\n      +\tgit add a.txt &&\n     -+\tgit commit -m dev:1\n     -+'\n     -+\n     -+test_expect_success 'dev merge master' '\n     ++\tgit commit -m dev:1 &&\n      +\ttest_must_fail git merge master &&\n      +\tgit ls-files -t --dedup >actual1 &&\n      +\ttest_cmp expect1 actual1 &&\n\n\n Documentation/git-ls-files.txt |  5 ++++\n builtin/ls-files.c             | 41 ++++++++++++++++++++++++--\n t/t3012-ls-files-dedup.sh      | 54 ++++++++++++++++++++++++++++++++++\n 3 files changed, 97 insertions(+), 3 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\ndiff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\nindex cbcf5263dd0..0f8dbeeea20 100644\n--- a/Documentation/git-ls-files.txt\n+++ b/Documentation/git-ls-files.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t\t(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*\n \t\t(-[c|d|o|i|s|u|k|m])*\n \t\t[--eol]\n+\t\t[--dedup]\n \t\t[-x <pattern>|--exclude=<pattern>]\n \t\t[-X <file>|--exclude-from=<file>]\n \t\t[--exclude-per-directory=<file>]\n@@ -81,6 +82,10 @@ OPTIONS\n \t\\0 line termination on output and do not quote filenames.\n \tSee OUTPUT below for more information.\n \n+--dedup::\n+\tSuppress duplicate entries when conflict happen or `--deleted`\n+\tand `--modified` are combined.\n+\n -x <pattern>::\n --exclude=<pattern>::\n \tSkip untracked files matching pattern.\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex c8eae899b82..bc4eded19ab 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -35,6 +35,7 @@ static int line_terminator = '\\n';\n static int debug_mode;\n static int show_eol;\n static int recurse_submodules;\n+static int delete_dup;\n \n static const char *prefix;\n static int max_prefix_len;\n@@ -301,6 +302,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n {\n \tint i;\n \tstruct strbuf fullname = STRBUF_INIT;\n+\tconst struct cache_entry *last_stage = NULL;\n \n \t/* For cached/deleted files we don't need to even do the readdir */\n \tif (show_others || show_killed) {\n@@ -316,6 +318,20 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n \n+\t\t\tif (show_cached && delete_dup) {\n+\t\t\t\tswitch (ce_stage(ce)) {\n+\t\t\t\tcase 0:\n+\t\t\t\tdefault:\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 1:\n+\t\t\t\tcase 2:\n+\t\t\t\tcase 3:\n+\t\t\t\t\tif (last_stage &&\n+\t\t\t\t\t!strcmp(last_stage->name, ce->name))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tlast_stage = ce;\n+\t\t\t\t}\n+\t\t\t}\n \t\t\tconstruct_fullname(&fullname, repo, ce);\n \n \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n@@ -337,6 +353,20 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tstruct stat st;\n \t\t\tint err;\n \n+\t\t\tif (delete_dup) {\n+\t\t\t\tswitch (ce_stage(ce)) {\n+\t\t\t\tcase 0:\n+\t\t\t\tdefault:\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 1:\n+\t\t\t\tcase 2:\n+\t\t\t\tcase 3:\n+\t\t\t\t\tif (last_stage &&\n+\t\t\t\t\t!strcmp(last_stage->name, ce->name))\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tlast_stage = ce;\n+\t\t\t\t}\n+\t\t\t}\n \t\t\tconstruct_fullname(&fullname, repo, ce);\n \n \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n@@ -347,10 +377,14 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tif (ce_skip_worktree(ce))\n \t\t\t\tcontinue;\n \t\t\terr = lstat(fullname.buf, &st);\n-\t\t\tif (show_deleted && err)\n+\t\t\tif (delete_dup && show_deleted && show_modified && err)\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\telse {\n+\t\t\t\tif (show_deleted && err)\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\t}\n \t\t}\n \t}\n \n@@ -578,6 +612,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n+\t\tOPT_BOOL(0, \"dedup\", &delete_dup, N_(\"suppress duplicate entries\")),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\nnew file mode 100755\nindex 00000000000..aec7d364235\n--- /dev/null\n+++ b/t/t3012-ls-files-dedup.sh\n@@ -0,0 +1,54 @@\n+#!/bin/sh\n+\n+test_description='git ls-files --dedup test.\n+\n+This test prepares the following in the cache:\n+\n+    a.txt       - a file(base)\n+    a.txt\t- a file(master)\n+    a.txt       - a file(dev)\n+    b.txt       - a file\n+    delete.txt  - a file\n+    expect1\t- a file\n+    expect2\t- a file\n+\n+'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\t> a.txt &&\n+\t> b.txt &&\n+\t> delete.txt &&\n+\tcat >expect1<<-\\EOF &&\n+\tM a.txt\n+\tH b.txt\n+\tH delete.txt\n+\tH expect1\n+\tH expect2\n+\tEOF\n+\tcat >expect2<<-EOF &&\n+\tC a.txt\n+\tR delete.txt\n+\tEOF\n+\tgit add a.txt b.txt delete.txt expect1 expect2 &&\n+\tgit commit -m master:1 &&\n+\techo a>a.txt &&\n+\techo b>b.txt &&\n+\techo delete >delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m master:2 &&\n+\tgit checkout HEAD~ &&\n+\tgit switch -c dev &&\n+\techo change >a.txt &&\n+\tgit add a.txt &&\n+\tgit commit -m dev:1 &&\n+\ttest_must_fail git merge master &&\n+\tgit ls-files -t --dedup >actual1 &&\n+\ttest_cmp expect1 actual1 &&\n+\trm delete.txt &&\n+\tgit ls-files -d -m -t --dedup >actual2 &&\n+\ttest_cmp expect2 actual2\n+'\n+\n+test_done\n\nbase-commit: 6d3ef5b467eccd2769f1aa1c555d317d3c8dc707\n-- \ngitgitgadget\n"},{"id":"414411","messageId":"xmqqczy7vwub.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"pull.832.v3.git.1610626942677.gitgitgadget@gmail.com","subject":"Re: [PATCH v3] ls-files.c: add --dedup option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-15T00:59:40Z","receivedAt":"2021-01-15T01:00:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"阿德烈 via GitGitGadget\"  <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> In order to provide users a better experience\n> when viewing information about files in the index\n> and the working tree, the `--dedup` option will suppress\n> some duplicate options under some conditions.\n>\n> In a merge conflict, one item of \"git ls-files\" output may\n> appear multiple times. For example,now the file `a.c` has\n> a conflict,`a.c` will appear three times in the output of\n> \"git ls-files\".We can use \"git ls-files --dedup\" to output\n> `a.c` only one time.(unless `--stage` or `--unmerged` is\n> used to view all the detailed information in the index)\n\nUnlike these option names we see in the description, \"dedup\" is not\na full word.  Perhaps spell it fully \"--deduplicate\" while letting\nparse-options machinery to accept unique prefix (including\n\"--dedup\"?\n\n> In addition, if you use both `--delete` and `--modify` in\n> the same time, The `--dedup` option can also suppress modified\n\n\"at the same time\", I think.\n\n> entries output.\n\n[let's call this point \"point A\"]\n\n> `--dedup` option relevant descriptions in\n> `Documentation/git-ls-files.txt`,\n\nI am not sure what this means.\n\n> the test script in `t/t3012-ls-files-dedup.sh`\n> prove the correctness of the `--dedup` option.\n\nNo amount of tests \"proves\" any correctness, but that is OK.  I\nthink you meant to say \"a few tests have been added to t3012 to\nprotect the new feature from future breakage\" or something like\nthat.\n\nIn any case, I think everything after \"point A\" and before your sign\noff does not belong to the log message.  The diffstat shows that\ndocumentation and tests have been added already.\n\n> +--dedup::\n> +\tSuppress duplicate entries when conflict happen\n\n\"conflict happen\" -> \"there are unmerged paths\", as the term\n\"unmerged\" is already shown to readers of \"ls-files --help\".\n\n> +\tor `--deleted` and `--modified` are combined.\n\nI somehow thought that you refrained from deduping when you are\nshowing the stages with \"ls-files -u\" and \"ls-files -s\", or you are\nshowing status with \"ls-files -t\", because you will otherwise lose\ninformation.  In other words, showing only one cache entry out of\nmany that share the same name makes sense only when we are showing\nname and nothing else.\n\nHas that been changed from the previous rounds?\n\n> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> index c8eae899b82..bc4eded19ab 100644\n> --- a/builtin/ls-files.c\n> +++ b/builtin/ls-files.c\n> @@ -316,6 +318,20 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>  \t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n>  \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n>  \n> +\t\t\tif (show_cached && delete_dup) {\n> +\t\t\t\tswitch (ce_stage(ce)) {\n> +\t\t\t\tcase 0:\n> +\t\t\t\tdefault:\n> +\t\t\t\t\tbreak;\n\nThis part looks somewhat strange for two reasons:\n\n - The code enumerates ALL the possible stage numbers from 0 to 3;\n   if we were to have \"default\", I'd expect it would be a separate\n   switch arm from the possible values that calls out an programming\n   error, e.g. BUG(\"at stage #%d???\", ce_stage(ce)).  Simply removing\n   the \"default\" arm would be another way out of this strangeness.\n\n - When we see a stage #0 entry, we know we will not have higher\n   stage entries with the same name.  Not clearing last_stage here\n   feels wrong, as the primary reason why last_stage variable is\n   used is to remember the last ce that was shown, so that other\n   entries with the same name can be skipped.\n\nBy the way, \"last_shown_ce\" may be a much better name for the\nvariable, as you do not really care what stage number the ce you\nshowed last was at (you care about its name).\n\nAlso, I do not see a good reason why the last_shown_ce variable\nshould have lifetime longer than the block that contains this for()\nloop (and the other for loop for deleted/modified codepath we see\nlater).  Especially since you initialize the variable that you made\nvisible to the entire function to NULL before entering the first for\nloop, but you do not set it back to NULL before entering the second\nfor loop, it is inviting a subtle bug.  You may have been given\nshow_cached and show_modified at the same time, so you enter the\nfirst loop and have shown the first stage of the last conflicted\npath, whose cache entry is left in the last_stage variable.  Since\nthe variable has longer lifespan than necessary, when the second\nloop is entered, it still points at the cache entry for the highest\nstage of the last conflicted path.  That is because the code forgets\nto clear it to NULL before entering the second for loop.\n\nHaving said all that, I suspect that we may be much better off if we\ncan somehow merge the two loops into one.  You may be dedup adjacent\nentries in each loop separately with the approach taken by this\npatch, but I do not think the patch would work to deduplicate across\ntwo loops.  For example, what happens if you do this?\n\n    $ git reset --hard\n    $ echo >>builtin/ls-files.c\n    $ git ls-files -c -m builtin/ls-files.c\n    $ git ls-files -t -c -m builtin/ls-files.c\n\nI think you see the path twice in the output, with or without your\n--dedup option (remember what I said about proving, by the way? ;-)).\n\nOnce we successfully merged two loops into one, the part that shows\ntracked paths in the function would have only one loop, and it would\nbecome a lot cleaner to add the logic to \"skip showing the ce if it\nhas the same name as the previously shown one, only when doing so\nwon't lose information\", by doing something like this:\n\n\tstatic void show_files(....)\n\t{\n\t\t/* show_others || show_killed done here */\n\t\t...\n\n\t\t/* leave early if not showing anything */\n\t\tif (! (show_cached || show_stage || show_deleted || show_modified))\n\t\t\treturn;\n\n\t\tlast_shown_ce = NULL;\n\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n\n\t\t\tif (skipping_duplicates && last_shown_ce)\n\t\t\t\tif (!strcmp(ce->name, last_shown_ce->name))\n\t\t\t\t\tcontinue;\n\n\t\t\tconstruct_fullname();\n\n                        /* various reasons to skip the entry tested */\n\t\t\tif (showing ignored directory and ce is excluded)\n\t\t\t\tcontinue;\n\t\t\tif (show_unmerged && !ce_stage(ce))\n\t\t\t\tcontinue;\n\t\t\tif (ce->ce_flags & CE_UPDATE)\n\t\t\t\tcontinue;\n\t\t\t... other reasons may appear here ...\n\n\t\t\t/* now we are committed to show it */\n\t\t\tlast_shown_ce = ce;\n\n\t\t\t... various different ways to show ce come here ... \n\t\t\tshow_ce(...);\n\t\t}\n\t}\n\nwhere \"skipping_duplicates\" would be set when \"--deduplicate\" is\nasked and we are not showing information other than the pathname\nvia various options e.g. the tags (-t) or stages (-s/-u).\n\n> +\t\t\tif (delete_dup && show_deleted && show_modified && err)\n>  \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n\nI actually think the original code that is still shown here ...\n\n> +\t\t\telse {\n> +\t\t\t\tif (show_deleted && err)\n> +\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n> +\t\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n\n... about modified file is buggy.  If lstat() failed, then &st has\nno useful information, so it is wrong to feed it to ie_modified().\n\nPerhaps a three-patch series that is structured like this may be in\norder?\n\n #1: bugfix for --deleted and --modified.\n\n\terr = lstat(fullname.buf, &st);\n\tif (err) {\n\t\t/* deleted? */\n\t\tif (errno != E_NOENT)\n\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n\t\telse {\n\t\t\tif (show_deleted)\n                        \tshow_ce(..., tag_removed);\n\t\t\tif (show_modified)\n                        \tshow_ce(..., tag_modified);\n\t\t}\n\t} else if (show_modified && ie_modified(...))\n\t\tshow_ce(..., tag_modified);\n    \n     This hopefully should not change the semantics.  If you ask\n     --deleted and --modified, a deleted path would be listed twice.\n\n #2: consolidate two for loops into one.\n\n     The two loops have slightly different condition to skip a ce,\n     and different logic on what tag each path is shown with.  When\n     --cached and --modified or --deleted are asked for at the same\n     time, we'd show them multiple times (this is done inside the\n     loop for each ce)\n\n\tif (show_cached || show_stage)\n\t\tshow_ce(... ce_stage(ce) ? tag_unmerged : ...);\n\terr = lstat(fullname.buf, &st);\n\tif (err) {\n        \t/* deleted? */\n                ... code that corresponds to the\n\t\t... illustration in #1 above come here.\n\t} else if (...)\n\t\tshow_ce(..., tag_modified);\n\n     This changes the semantics.  The original iterates the index\n     twice, so you may see the same entry from --cached once and\n     then again from --modified.  The updated one still will show\n     the same entry twice but next to each other.\n\n #3: optionally deduplicate.\n\n     Once we have a single loop, deduplicationg based on names is\n     trivial, as we seen before.\n\n\nHmm?\n"},{"id":"414493","messageId":"CAPig+cQBi7jdq64==U630Ht1YDcH+9komLNv-hZMnEhQ1Q-V9A@mail.gmail.com","threadId":"54943","inReplyTo":"pull.832.v3.git.1610626942677.gitgitgadget@gmail.com","subject":"Re: [PATCH v3] ls-files.c: add --dedup option","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-01-16T07:13:18Z","receivedAt":"2021-01-16T07:16:58Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Jan 14, 2021 at 7:22 AM 阿德烈 via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> In order to provide users a better experience\n> when viewing information about files in the index\n> and the working tree, the `--dedup` option will suppress\n> some duplicate options under some conditions.\n> [...]\n\nI have a few very minor comments alongside Junio's review comments...\n\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n> diff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\n> @@ -0,0 +1,54 @@\n> +test_description='git ls-files --dedup test.\n> +\n> +This test prepares the following in the cache:\n> +\n> +    a.txt       - a file(base)\n> +    a.txt      - a file(master)\n> +    a.txt       - a file(dev)\n> +    b.txt       - a file\n> +    delete.txt  - a file\n> +    expect1    - a file\n> +    expect2    - a file\n> +\n> +'\n\nThis test script description is outdated now. Perhaps shorten it to:\n\n    test_description='ls-files dedup tests'\n\nOr, it might be suitable to simply add the new test to the existing\nt3004-ls-files-basic.sh instead.\n\n> +test_expect_success 'setup' '\n> +       > a.txt &&\n> +       > b.txt &&\n> +       > delete.txt &&\n> +       cat >expect1<<-\\EOF &&\n\nStyle nits: no space after redirection operator and a space before\nredirection operator:\n\n    >a.txt &&\n    >b.txt &&\n    >delete.txt &&\n    cat >expect1 <<-\\EOF &&\n\n> +       cat >expect2<<-EOF &&\n\nNit: missing the backslash (and wrong spacing):\n\n    cat >expect2 <<-\\EOF &&\n\n> +       echo a>a.txt &&\n> +       echo b>b.txt &&\n\nStyle:\n\n    echo a >a.txt &&\n    echo b >b.txt &&\n\n> +       echo delete >delete.txt &&\n> +       git add a.txt b.txt delete.txt &&\n> +       git commit -m master:2 &&\n> +       git checkout HEAD~ &&\n> +       git switch -c dev &&\n\nIf someone adds a new test after this test, then that new test will\nrun in the \"dev\" branch, which might be unexpected or undesirable. It\noften is a good idea to ensure that tests do certain types of cleanup\nto avoid breaking subsequent tests. Here, it would be a good idea to\nensure that the test switches back to the original branch when it\nfinishes (regardless of whether it finishes successfully or\nunsuccessfully).\n\n    git switch -c dev &&\n    test_when_finished \"git switch master\" &&\n\nOr you could use `git switch -` if you don't want to hard-code the\nname \"master\" in the test (since there has been effort lately to\nremove that name from tests.\n\n> +       echo change >a.txt &&\n> +       git add a.txt &&\n> +       git commit -m dev:1 &&\n> +       test_must_fail git merge master &&\n> +       git ls-files -t --dedup >actual1 &&\n> +       test_cmp expect1 actual1 &&\n> +       rm delete.txt &&\n> +       git ls-files -d -m -t --dedup >actual2 &&\n> +       test_cmp expect2 actual2\n\nWe usually don't bother giving temporary files unique names like\n\"actual1\" and \"actual2\" unless those files must exist at the same\ntime. This is because unique names like this may confuse readers into\nwondering if there is some hidden interdependency between the files.\nIn this case, the files don't need to exist at the same time, so it\nmay be better simply to use the names \"actual\" and \"expect\", like\nthis:\n\n    ...other stuff...\n    cat >expect <<-\\EOF &&\n    ...\n    EOF\n    git ls-files -t --dedup >actual &&\n    test_cmp expect actual &&\n    rm delete.txt &&\n    cat >expect <<-\\EOF &&\n    ...\n    EOF\n    git ls-files -d -m -t --dedup >actual &&\n    test_cmp expect actual\n\n(It also has the benefit that the \"expect\" content is closer to the\nplace where it is actually used, which may make it a bit easier for a\nperson reading the test to understand what is supposed to be\nproduced.)\n"},{"id":"414534","messageId":"CAOLTT8RQWm-tpkj1aO1rPTCJApP5i+niQtNm_zMycSo5YT0B_w@mail.gmail.com","threadId":"54943","inReplyTo":"xmqqczy7vwub.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3] ls-files.c: add --dedup option","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-17T03:45:25Z","receivedAt":"2021-01-17T03:45:44Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio, thank you for your patience to review\nmy patch and guide me how to modify it.\n\nJunio C Hamano <gitster@pobox.com> 于2021年1月15日周五 上午8:59写道：\n>\n> \"阿德烈 via GitGitGadget\"  <gitgitgadget@gmail.com> writes:\n>\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > In order to provide users a better experience\n> > when viewing information about files in the index\n> > and the working tree, the `--dedup` option will suppress\n> > some duplicate options under some conditions.\n> >\n> > In a merge conflict, one item of \"git ls-files\" output may\n> > appear multiple times. For example,now the file `a.c` has\n> > a conflict,`a.c` will appear three times in the output of\n> > \"git ls-files\".We can use \"git ls-files --dedup\" to output\n> > `a.c` only one time.(unless `--stage` or `--unmerged` is\n> > used to view all the detailed information in the index)\n>\n> Unlike these option names we see in the description, \"dedup\" is not\n> a full word.  Perhaps spell it fully \"--deduplicate\" while letting\n> parse-options machinery to accept unique prefix (including\n> \"--dedup\"?\n>\nOk i have modified \"--dedup\" to \"--deduplicate\".\n> > In addition, if you use both `--delete` and `--modify` in\n> > the same time, The `--dedup` option can also suppress modified\n>\n> \"at the same time\", I think.\n>\nMy poor English grammar :-)\n> > entries output.\n>\n> [let's call this point \"point A\"]\n>\n> > `--dedup` option relevant descriptions in\n> > `Documentation/git-ls-files.txt`,\n>\n> I am not sure what this means.\n>\n> > the test script in `t/t3012-ls-files-dedup.sh`\n> > prove the correctness of the `--dedup` option.\n>\n> No amount of tests \"proves\" any correctness, but that is OK.  I\n> think you meant to say \"a few tests have been added to t3012 to\n> protect the new feature from future breakage\" or something like\n> that.\n>\nAlright, I understand!\n> In any case, I think everything after \"point A\" and before your sign\n> off does not belong to the log message.  The diffstat shows that\n> documentation and tests have been added already.\n>\n> > +--dedup::\n> > +     Suppress duplicate entries when conflict happen\n>\n> \"conflict happen\" -> \"there are unmerged paths\", as the term\n> \"unmerged\" is already shown to readers of \"ls-files --help\".\n>\nWell, maybe I'm not good enough with these proper nouns.\n> > +     or `--deleted` and `--modified` are combined.\n>\n> I somehow thought that you refrained from deduping when you are\n> showing the stages with \"ls-files -u\" and \"ls-files -s\", or you are\n> showing status with \"ls-files -t\", because you will otherwise lose\n> information.  In other words, showing only one cache entry out of\n> many that share the same name makes sense only when we are showing\n> name and nothing else.\n>\nYou are right, \"--deduplicate\" should only work on duplicate file names,\nso \"ls-files -t\" also needs to be corrected.\nWell,This is true a bug I haven't notice.\n> Having said all that, I suspect that we may be much better off if we\n> can somehow merge the two loops into one.  You may be dedup adjacent\n> entries in each loop separately with the approach taken by this\n> patch, but I do not think the patch would work to deduplicate across\n> two loops.  For example, what happens if you do this?\n>\n>     $ git reset --hard\n>     $ echo >>builtin/ls-files.c\n>     $ git ls-files -c -m builtin/ls-files.c\n>     $ git ls-files -t -c -m builtin/ls-files.c\n>\n> I think you see the path twice in the output, with or without your\n> --dedup option (remember what I said about proving, by the way? ;-)).\n>\nYeah,This is because I may have missed the -c option with other options\nat the same time.\nHere I may disagree with your point of view:\n                 if (errno != E_NOENT)\n                         error_errno(\"cannot lstat '%s'\", fullname.buf);\nWith this sentence included, the patch will fail the test:\nt/t3010-ls-files-killed-modified.sh.\nthe errno maybe ENOTDIR when you try to lstat a file`r` with `lstat(\"r/f\",&st);`\nSo I temporarily removed the judgment of errno.\n>  #2: consolidate two for loops into one.\n>\n>      The two loops have slightly different condition to skip a ce,\n>      and different logic on what tag each path is shown with.  When\n>      --cached and --modified or --deleted are asked for at the same\n>      time, we'd show them multiple times (this is done inside the\n>      loop for each ce)\n>\n>         if (show_cached || show_stage)\n>                 show_ce(... ce_stage(ce) ? tag_unmerged : ...);\n>         err = lstat(fullname.buf, &st);\n>         if (err) {\n>                 /* deleted? */\n>                 ... code that corresponds to the\n>                 ... illustration in #1 above come here.\n>         } else if (...)\n>                 show_ce(..., tag_modified);\n>\n>      This changes the semantics.  The original iterates the index\n>      twice, so you may see the same entry from --cached once and\n>      then again from --modified.  The updated one still will show\n>      the same entry twice but next to each other.\n>\nWell,This does change the semantics. I think people who used two\nfor loops before may want to separate different outputs.\nNow, if you don’t use \"--deduplicate\", You may see six consecutive\nitems under a combination of multiple options.\n>  #3: optionally deduplicate.\n>\n>      Once we have a single loop, deduplicationg based on names is\n>      trivial, as we seen before.\n>\n>\nIndeed so.\n> Hmm?\nTHANKS.\n\nJunio C Hamano <gitster@pobox.com> 于2021年1月15日周五 上午8:59写道：\n>\n> \"阿德烈 via GitGitGadget\"  <gitgitgadget@gmail.com> writes:\n>\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > In order to provide users a better experience\n> > when viewing information about files in the index\n> > and the working tree, the `--dedup` option will suppress\n> > some duplicate options under some conditions.\n> >\n> > In a merge conflict, one item of \"git ls-files\" output may\n> > appear multiple times. For example,now the file `a.c` has\n> > a conflict,`a.c` will appear three times in the output of\n> > \"git ls-files\".We can use \"git ls-files --dedup\" to output\n> > `a.c` only one time.(unless `--stage` or `--unmerged` is\n> > used to view all the detailed information in the index)\n>\n> Unlike these option names we see in the description, \"dedup\" is not\n> a full word.  Perhaps spell it fully \"--deduplicate\" while letting\n> parse-options machinery to accept unique prefix (including\n> \"--dedup\"?\n>\n> > In addition, if you use both `--delete` and `--modify` in\n> > the same time, The `--dedup` option can also suppress modified\n>\n> \"at the same time\", I think.\n>\n> > entries output.\n>\n> [let's call this point \"point A\"]\n>\n> > `--dedup` option relevant descriptions in\n> > `Documentation/git-ls-files.txt`,\n>\n> I am not sure what this means.\n>\n> > the test script in `t/t3012-ls-files-dedup.sh`\n> > prove the correctness of the `--dedup` option.\n>\n> No amount of tests \"proves\" any correctness, but that is OK.  I\n> think you meant to say \"a few tests have been added to t3012 to\n> protect the new feature from future breakage\" or something like\n> that.\n>\n> In any case, I think everything after \"point A\" and before your sign\n> off does not belong to the log message.  The diffstat shows that\n> documentation and tests have been added already.\n>\n> > +--dedup::\n> > +     Suppress duplicate entries when conflict happen\n>\n> \"conflict happen\" -> \"there are unmerged paths\", as the term\n> \"unmerged\" is already shown to readers of \"ls-files --help\".\n>\n> > +     or `--deleted` and `--modified` are combined.\n>\n> I somehow thought that you refrained from deduping when you are\n> showing the stages with \"ls-files -u\" and \"ls-files -s\", or you are\n> showing status with \"ls-files -t\", because you will otherwise lose\n> information.  In other words, showing only one cache entry out of\n> many that share the same name makes sense only when we are showing\n> name and nothing else.\n>\n> Has that been changed from the previous rounds?\n>\n> > diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> > index c8eae899b82..bc4eded19ab 100644\n> > --- a/builtin/ls-files.c\n> > +++ b/builtin/ls-files.c\n> > @@ -316,6 +318,20 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n> >               for (i = 0; i < repo->index->cache_nr; i++) {\n> >                       const struct cache_entry *ce = repo->index->cache[i];\n> >\n> > +                     if (show_cached && delete_dup) {\n> > +                             switch (ce_stage(ce)) {\n> > +                             case 0:\n> > +                             default:\n> > +                                     break;\n>\n> This part looks somewhat strange for two reasons:\n>\n>  - The code enumerates ALL the possible stage numbers from 0 to 3;\n>    if we were to have \"default\", I'd expect it would be a separate\n>    switch arm from the possible values that calls out an programming\n>    error, e.g. BUG(\"at stage #%d???\", ce_stage(ce)).  Simply removing\n>    the \"default\" arm would be another way out of this strangeness.\n>\n>  - When we see a stage #0 entry, we know we will not have higher\n>    stage entries with the same name.  Not clearing last_stage here\n>    feels wrong, as the primary reason why last_stage variable is\n>    used is to remember the last ce that was shown, so that other\n>    entries with the same name can be skipped.\n>\n> By the way, \"last_shown_ce\" may be a much better name for the\n> variable, as you do not really care what stage number the ce you\n> showed last was at (you care about its name).\n>\n> Also, I do not see a good reason why the last_shown_ce variable\n> should have lifetime longer than the block that contains this for()\n> loop (and the other for loop for deleted/modified codepath we see\n> later).  Especially since you initialize the variable that you made\n> visible to the entire function to NULL before entering the first for\n> loop, but you do not set it back to NULL before entering the second\n> for loop, it is inviting a subtle bug.  You may have been given\n> show_cached and show_modified at the same time, so you enter the\n> first loop and have shown the first stage of the last conflicted\n> path, whose cache entry is left in the last_stage variable.  Since\n> the variable has longer lifespan than necessary, when the second\n> loop is entered, it still points at the cache entry for the highest\n> stage of the last conflicted path.  That is because the code forgets\n> to clear it to NULL before entering the second for loop.\n>\n> Having said all that, I suspect that we may be much better off if we\n> can somehow merge the two loops into one.  You may be dedup adjacent\n> entries in each loop separately with the approach taken by this\n> patch, but I do not think the patch would work to deduplicate across\n> two loops.  For example, what happens if you do this?\n>\n>     $ git reset --hard\n>     $ echo >>builtin/ls-files.c\n>     $ git ls-files -c -m builtin/ls-files.c\n>     $ git ls-files -t -c -m builtin/ls-files.c\n>\n> I think you see the path twice in the output, with or without your\n> --dedup option (remember what I said about proving, by the way? ;-)).\n>\n> Once we successfully merged two loops into one, the part that shows\n> tracked paths in the function would have only one loop, and it would\n> become a lot cleaner to add the logic to \"skip showing the ce if it\n> has the same name as the previously shown one, only when doing so\n> won't lose information\", by doing something like this:\n>\n>         static void show_files(....)\n>         {\n>                 /* show_others || show_killed done here */\n>                 ...\n>\n>                 /* leave early if not showing anything */\n>                 if (! (show_cached || show_stage || show_deleted || show_modified))\n>                         return;\n>\n>                 last_shown_ce = NULL;\n>                 for (i = 0; i < repo->index->cache_nr; i++) {\n>                         const struct cache_entry *ce = repo->index->cache[i];\n>\n>                         if (skipping_duplicates && last_shown_ce)\n>                                 if (!strcmp(ce->name, last_shown_ce->name))\n>                                         continue;\n>\n>                         construct_fullname();\n>\n>                         /* various reasons to skip the entry tested */\n>                         if (showing ignored directory and ce is excluded)\n>                                 continue;\n>                         if (show_unmerged && !ce_stage(ce))\n>                                 continue;\n>                         if (ce->ce_flags & CE_UPDATE)\n>                                 continue;\n>                         ... other reasons may appear here ...\n>\n>                         /* now we are committed to show it */\n>                         last_shown_ce = ce;\n>\n>                         ... various different ways to show ce come here ...\n>                         show_ce(...);\n>                 }\n>         }\n>\n> where \"skipping_duplicates\" would be set when \"--deduplicate\" is\n> asked and we are not showing information other than the pathname\n> via various options e.g. the tags (-t) or stages (-s/-u).\n>\n> > +                     if (delete_dup && show_deleted && show_modified && err)\n> >                               show_ce(repo, dir, ce, fullname.buf, tag_removed);\n>\n> I actually think the original code that is still shown here ...\n>\n> > +                     else {\n> > +                             if (show_deleted && err)\n> > +                                     show_ce(repo, dir, ce, fullname.buf, tag_removed);\n> > +                             if (show_modified && ie_modified(repo->index, ce, &st, 0))\n>\n> ... about modified file is buggy.  If lstat() failed, then &st has\n> no useful information, so it is wrong to feed it to ie_modified().\n>\n> Perhaps a three-patch series that is structured like this may be in\n> order?\n>\n>  #1: bugfix for --deleted and --modified.\n>\n>         err = lstat(fullname.buf, &st);\n>         if (err) {\n>                 /* deleted? */\n>                 if (errno != E_NOENT)\n>                         error_errno(\"cannot lstat '%s'\", fullname.buf);\n>                 else {\n>                         if (show_deleted)\n>                                 show_ce(..., tag_removed);\n>                         if (show_modified)\n>                                 show_ce(..., tag_modified);\n>                 }\n>         } else if (show_modified && ie_modified(...))\n>                 show_ce(..., tag_modified);\n>\n>      This hopefully should not change the semantics.  If you ask\n>      --deleted and --modified, a deleted path would be listed twice.\n>\n>  #2: consolidate two for loops into one.\n>\n>      The two loops have slightly different condition to skip a ce,\n>      and different logic on what tag each path is shown with.  When\n>      --cached and --modified or --deleted are asked for at the same\n>      time, we'd show them multiple times (this is done inside the\n>      loop for each ce)\n>\n>         if (show_cached || show_stage)\n>                 show_ce(... ce_stage(ce) ? tag_unmerged : ...);\n>         err = lstat(fullname.buf, &st);\n>         if (err) {\n>                 /* deleted? */\n>                 ... code that corresponds to the\n>                 ... illustration in #1 above come here.\n>         } else if (...)\n>                 show_ce(..., tag_modified);\n>\n>      This changes the semantics.  The original iterates the index\n>      twice, so you may see the same entry from --cached once and\n>      then again from --modified.  The updated one still will show\n>      the same entry twice but next to each other.\n>\n>  #3: optionally deduplicate.\n>\n>      Once we have a single loop, deduplicationg based on names is\n>      trivial, as we seen before.\n>\n>\n> Hmm?\n"},{"id":"414535","messageId":"CAOLTT8QNhz4PrMhAt9y58n+-bEjeOE+xst8rzcg-x045Amw7iw@mail.gmail.com","threadId":"54943","inReplyTo":"CAPig+cQBi7jdq64==U630Ht1YDcH+9komLNv-hZMnEhQ1Q-V9A@mail.gmail.com","subject":"Re: [PATCH v3] ls-files.c: add --dedup option","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-17T03:49:41Z","receivedAt":"2021-01-17T03:48:58Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Eric,Thanks!\nI have little confuse about I can use` test_when_finished \"git switch master\" `,\nbut I can't use` test_when_finished \"git switch -\" `,\nwhy？\n\nEric Sunshine <sunshine@sunshineco.com> 于2021年1月16日周六 下午3:13写道：\n>\n> On Thu, Jan 14, 2021 at 7:22 AM 阿德烈 via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > In order to provide users a better experience\n> > when viewing information about files in the index\n> > and the working tree, the `--dedup` option will suppress\n> > some duplicate options under some conditions.\n> > [...]\n>\n> I have a few very minor comments alongside Junio's review comments...\n>\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > ---\n> > diff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\n> > @@ -0,0 +1,54 @@\n> > +test_description='git ls-files --dedup test.\n> > +\n> > +This test prepares the following in the cache:\n> > +\n> > +    a.txt       - a file(base)\n> > +    a.txt      - a file(master)\n> > +    a.txt       - a file(dev)\n> > +    b.txt       - a file\n> > +    delete.txt  - a file\n> > +    expect1    - a file\n> > +    expect2    - a file\n> > +\n> > +'\n>\n> This test script description is outdated now. Perhaps shorten it to:\n>\n>     test_description='ls-files dedup tests'\n>\n> Or, it might be suitable to simply add the new test to the existing\n> t3004-ls-files-basic.sh instead.\n>\n> > +test_expect_success 'setup' '\n> > +       > a.txt &&\n> > +       > b.txt &&\n> > +       > delete.txt &&\n> > +       cat >expect1<<-\\EOF &&\n>\n> Style nits: no space after redirection operator and a space before\n> redirection operator:\n>\n>     >a.txt &&\n>     >b.txt &&\n>     >delete.txt &&\n>     cat >expect1 <<-\\EOF &&\n>\n> > +       cat >expect2<<-EOF &&\n>\n> Nit: missing the backslash (and wrong spacing):\n>\n>     cat >expect2 <<-\\EOF &&\n>\n> > +       echo a>a.txt &&\n> > +       echo b>b.txt &&\n>\n> Style:\n>\n>     echo a >a.txt &&\n>     echo b >b.txt &&\n>\n> > +       echo delete >delete.txt &&\n> > +       git add a.txt b.txt delete.txt &&\n> > +       git commit -m master:2 &&\n> > +       git checkout HEAD~ &&\n> > +       git switch -c dev &&\n>\n> If someone adds a new test after this test, then that new test will\n> run in the \"dev\" branch, which might be unexpected or undesirable. It\n> often is a good idea to ensure that tests do certain types of cleanup\n> to avoid breaking subsequent tests. Here, it would be a good idea to\n> ensure that the test switches back to the original branch when it\n> finishes (regardless of whether it finishes successfully or\n> unsuccessfully).\n>\n>     git switch -c dev &&\n>     test_when_finished \"git switch master\" &&\n>\n> Or you could use `git switch -` if you don't want to hard-code the\n> name \"master\" in the test (since there has been effort lately to\n> remove that name from tests.\n>\n> > +       echo change >a.txt &&\n> > +       git add a.txt &&\n> > +       git commit -m dev:1 &&\n> > +       test_must_fail git merge master &&\n> > +       git ls-files -t --dedup >actual1 &&\n> > +       test_cmp expect1 actual1 &&\n> > +       rm delete.txt &&\n> > +       git ls-files -d -m -t --dedup >actual2 &&\n> > +       test_cmp expect2 actual2\n>\n> We usually don't bother giving temporary files unique names like\n> \"actual1\" and \"actual2\" unless those files must exist at the same\n> time. This is because unique names like this may confuse readers into\n> wondering if there is some hidden interdependency between the files.\n> In this case, the files don't need to exist at the same time, so it\n> may be better simply to use the names \"actual\" and \"expect\", like\n> this:\n>\n>     ...other stuff...\n>     cat >expect <<-\\EOF &&\n>     ...\n>     EOF\n>     git ls-files -t --dedup >actual &&\n>     test_cmp expect actual &&\n>     rm delete.txt &&\n>     cat >expect <<-\\EOF &&\n>     ...\n>     EOF\n>     git ls-files -d -m -t --dedup >actual &&\n>     test_cmp expect actual\n>\n> (It also has the benefit that the \"expect\" content is closer to the\n> place where it is actually used, which may make it a bit easier for a\n> person reading the test to understand what is supposed to be\n> produced.)\n"},{"id":"414536","messageId":"f4d9af8a3124bcd37a35b1863441406f6d939f11.1610856136.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v4.git.1610856136.gitgitgadget@gmail.com","subject":"[PATCH v4 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-17T04:02:14Z","receivedAt":"2021-01-17T04:03:19Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThis situation may occur in the original code: lstat() failed\nbut we use `&st` to feed ie_modified() later.\n\nIt's buggy!\n\nTherefore, we can directly execute show_ce without the judgment of\nie_modified() when lstat() has failed.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n builtin/ls-files.c | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex c8eae899b82..6f97a23c2dc 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -347,9 +347,12 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tif (ce_skip_worktree(ce))\n \t\t\t\tcontinue;\n \t\t\terr = lstat(fullname.buf, &st);\n-\t\t\tif (show_deleted && err)\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n+\t\t\tif (err) {\n+\t\t\t\t\tif (show_deleted)\n+\t\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\t\t\tif (show_modified)\n+\t\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\t}else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n \t\t}\n \t}\n-- \ngitgitgadget\n\n"},{"id":"414537","messageId":"pull.832.v4.git.1610856136.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v3.git.1610626942677.gitgitgadget@gmail.com","subject":"[PATCH v4 0/3] builtin/ls-files.c:add git ls-file --dedup option","fromName":"阿德烈 via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-17T04:02:13Z","receivedAt":"2021-01-17T04:03:19Z","isPatch":true,"sender":{"key":"name:阿德烈","avatar":null},"body":"I am reading the source code of git ls-files and learned that git ls-files\nmay have duplicate files name when there are unmerged path in a branch merge\nor when different options are used at the same time. Users may fell confuse\nwhen they see these duplicate file names.\n\nAs Junio C Hamano said ,it have odd behaviour.\n\nTherefore, we can provide an additional option to git ls-files to delete\nthose repeated information.\n\nThis fixes https://github.com/gitgitgadget/git/issues/198\n\nThanks!\n\nZheNing Hu (3):\n  ls_files.c: bugfix for --deleted and --modified\n  ls_files.c: consolidate two for loops into one\n  ls-files: add --deduplicate option\n\n Documentation/git-ls-files.txt |  5 +++\n builtin/ls-files.c             | 82 +++++++++++++++++++---------------\n t/t3012-ls-files-dedup.sh      | 57 +++++++++++++++++++++++\n 3 files changed, 109 insertions(+), 35 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\n\nbase-commit: 6d3ef5b467eccd2769f1aa1c555d317d3c8dc707\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-832%2Fadlternative%2Fls-files-dedup-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-832/adlternative/ls-files-dedup-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/832\n\nRange-diff vs v3:\n\n -:  ----------- > 1:  f4d9af8a312 ls_files.c: bugfix for --deleted and --modified\n -:  ----------- > 2:  50efd9b45b1 ls_files.c: consolidate two for loops into one\n 1:  5ce52c8b7a4 ! 3:  0c7830d07db ls-files.c: add --dedup option\n     @@ Metadata\n      Author: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Commit message ##\n     -    ls-files.c: add --dedup option\n     +    ls-files: add --deduplicate option\n      \n          In order to provide users a better experience\n          when viewing information about files in the index\n     -    and the working tree, the `--dedup` option will suppress\n     -    some duplicate options under some conditions.\n     +    and the working tree, the `--deduplicate` option will suppress\n     +    some duplicate name under some conditions.\n      \n     -    In a merge conflict, one item of \"git ls-files\" output may\n     -    appear multiple times. For example,now the file `a.c` has\n     -    a conflict,`a.c` will appear three times in the output of\n     -    \"git ls-files\".We can use \"git ls-files --dedup\" to output\n     +    In a merge conflict, one file name of \"git ls-files\" output may\n     +    appear multiple times. For example,now there is an unmerged path\n     +    `a.c`,`a.c` will appear three times in the output of\n     +    \"git ls-files\".We can use \"git ls-files --deduplicate\" to output\n          `a.c` only one time.(unless `--stage` or `--unmerged` is\n          used to view all the detailed information in the index)\n      \n     -    In addition, if you use both `--delete` and `--modify` in\n     -    the same time, The `--dedup` option can also suppress modified\n     -    entries output.\n     +    In addition, if you use both `--delete` and `--modify` at\n     +    the same time, The `--deduplicate` option\n     +    can also suppress file name output.\n      \n     -    `--dedup` option relevant descriptions in\n     -    `Documentation/git-ls-files.txt`,\n     -    the test script in `t/t3012-ls-files-dedup.sh`\n     -    prove the correctness of the `--dedup` option.\n     -\n     -    this patch fixed:\n     -    https://github.com/gitgitgadget/git/issues/198\n     -    Thanks.\n     +    Additional instructions:\n     +    In order to display entries information,`deduplicate` suppresses\n     +    the output of duplicate file names, not the output of duplicate\n     +    entries information, so under the option of `-t`, `--stage`, `--unmerge`,\n     +    `--deduplicate` will have no effect.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n      \n     @@ Documentation/git-ls-files.txt: SYNOPSIS\n       \t\t(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*\n       \t\t(-[c|d|o|i|s|u|k|m])*\n       \t\t[--eol]\n     -+\t\t[--dedup]\n     ++\t\t[--deduplicate]\n       \t\t[-x <pattern>|--exclude=<pattern>]\n       \t\t[-X <file>|--exclude-from=<file>]\n       \t\t[--exclude-per-directory=<file>]\n     @@ Documentation/git-ls-files.txt: OPTIONS\n       \t\\0 line termination on output and do not quote filenames.\n       \tSee OUTPUT below for more information.\n       \n     -+--dedup::\n     -+\tSuppress duplicate entries when conflict happen or `--deleted`\n     -+\tand `--modified` are combined.\n     ++--deduplicate::\n     ++\tSuppress duplicate entries when there are unmerged paths in index\n     ++\tor `--deleted` and `--modified` are combined.\n      +\n       -x <pattern>::\n       --exclude=<pattern>::\n     @@ builtin/ls-files.c: static int line_terminator = '\\n';\n       static int debug_mode;\n       static int show_eol;\n       static int recurse_submodules;\n     -+static int delete_dup;\n     ++static int skipping_duplicates;\n       \n       static const char *prefix;\n       static int max_prefix_len;\n     @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_s\n       {\n       \tint i;\n       \tstruct strbuf fullname = STRBUF_INIT;\n     -+\tconst struct cache_entry *last_stage = NULL;\n     ++\tconst struct cache_entry *last_shown_ce;\n       \n       \t/* For cached/deleted files we don't need to even do the readdir */\n       \tif (show_others || show_killed) {\n      @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     - \t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n     - \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n     - \n     -+\t\t\tif (show_cached && delete_dup) {\n     -+\t\t\t\tswitch (ce_stage(ce)) {\n     -+\t\t\t\tcase 0:\n     -+\t\t\t\tdefault:\n     -+\t\t\t\t\tbreak;\n     -+\t\t\t\tcase 1:\n     -+\t\t\t\tcase 2:\n     -+\t\t\t\tcase 3:\n     -+\t\t\t\t\tif (last_stage &&\n     -+\t\t\t\t\t!strcmp(last_stage->name, ce->name))\n     -+\t\t\t\t\t\tcontinue;\n     -+\t\t\t\t\tlast_stage = ce;\n     -+\t\t\t\t}\n     -+\t\t\t}\n     - \t\t\tconstruct_fullname(&fullname, repo, ce);\n     - \n     - \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n     + \t}\n     + \tif (! (show_cached || show_stage || show_deleted || show_modified))\n     + \t\treturn;\n     ++\tlast_shown_ce = NULL;\n     + \tfor (i = 0; i < repo->index->cache_nr; i++) {\n     + \t\tconst struct cache_entry *ce = repo->index->cache[i];\n     + \t\tstruct stat st;\n      @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     - \t\t\tstruct stat st;\n     - \t\t\tint err;\n       \n     -+\t\t\tif (delete_dup) {\n     -+\t\t\t\tswitch (ce_stage(ce)) {\n     -+\t\t\t\tcase 0:\n     -+\t\t\t\tdefault:\n     -+\t\t\t\t\tbreak;\n     -+\t\t\t\tcase 1:\n     -+\t\t\t\tcase 2:\n     -+\t\t\t\tcase 3:\n     -+\t\t\t\t\tif (last_stage &&\n     -+\t\t\t\t\t!strcmp(last_stage->name, ce->name))\n     -+\t\t\t\t\t\tcontinue;\n     -+\t\t\t\t\tlast_stage = ce;\n     -+\t\t\t\t}\n     -+\t\t\t}\n     - \t\t\tconstruct_fullname(&fullname, repo, ce);\n     + \t\tconstruct_fullname(&fullname, repo, ce);\n       \n     - \t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n     -@@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     - \t\t\tif (ce_skip_worktree(ce))\n     - \t\t\t\tcontinue;\n     - \t\t\terr = lstat(fullname.buf, &st);\n     --\t\t\tif (show_deleted && err)\n     -+\t\t\tif (delete_dup && show_deleted && show_modified && err)\n     - \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     --\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n     --\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     ++\t\tif (skipping_duplicates && last_shown_ce &&\n     ++\t\t\t!strcmp(last_shown_ce->name,ce->name))\n     ++\t\t\t\tcontinue;\n     + \t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n     + \t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n     + \t\t\tcontinue;\n     + \t\tif (ce->ce_flags & CE_UPDATE)\n     + \t\t\tcontinue;\n     + \t\tif (show_cached || show_stage) {\n     ++\t\t\tif (show_cached && skipping_duplicates && last_shown_ce &&\n     ++\t\t\t\t!strcmp(last_shown_ce->name,ce->name))\n     ++\t\t\t\t\tcontinue;\n     + \t\t\tif (!show_unmerged || ce_stage(ce))\n     + \t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n     + \t\t\t\t\tce_stage(ce) ? tag_unmerged :\n     + \t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n     + \t\t\t\t\t\ttag_cached));\n     ++\t\t\tif(show_cached && skipping_duplicates)\n     ++\t\t\t\tlast_shown_ce = ce;\n     + \t\t}\n     + \t\tif (ce_skip_worktree(ce))\n     + \t\t\tcontinue;\n     ++\t\tif (skipping_duplicates && last_shown_ce && !strcmp(last_shown_ce->name,ce->name))\n     ++\t\t\tcontinue;\n     + \t\terr = lstat(fullname.buf, &st);\n     + \t\tif (err) {\n     ++\t\t\tif (skipping_duplicates && show_deleted && show_modified)\n     ++\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n      +\t\t\telse {\n     -+\t\t\t\tif (show_deleted && err)\n     -+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     -+\t\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n     -+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     + \t\t\t\tif (show_deleted)\n     + \t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     + \t\t\t\tif (show_modified)\n     + \t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     +-\t\t}else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n      +\t\t\t}\n     - \t\t}\n     ++\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n     + \t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     ++\t\tlast_shown_ce = ce;\n       \t}\n       \n     + \tstrbuf_release(&fullname);\n      @@ builtin/ls-files.c: int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n       \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n       \t\tOPT__ABBREV(&abbrev),\n       \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n     -+\t\tOPT_BOOL(0, \"dedup\", &delete_dup, N_(\"suppress duplicate entries\")),\n     ++\t\tOPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n       \t\tOPT_END()\n       \t};\n       \n     +@@ builtin/ls-files.c: int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n     + \t\ttag_skip_worktree = \"S \";\n     + \t\ttag_resolve_undo = \"U \";\n     + \t}\n     ++\tif (show_tag && skipping_duplicates)\n     ++\t\tskipping_duplicates = 0;\n     + \tif (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)\n     + \t\trequire_work_tree = 1;\n     + \tif (show_unmerged)\n      \n       ## t/t3012-ls-files-dedup.sh (new) ##\n      @@\n      +#!/bin/sh\n      +\n     -+test_description='git ls-files --dedup test.\n     -+\n     -+This test prepares the following in the cache:\n     -+\n     -+    a.txt       - a file(base)\n     -+    a.txt\t- a file(master)\n     -+    a.txt       - a file(dev)\n     -+    b.txt       - a file\n     -+    delete.txt  - a file\n     -+    expect1\t- a file\n     -+    expect2\t- a file\n     -+\n     -+'\n     ++test_description='git ls-files --deduplicate test'\n      +\n      +. ./test-lib.sh\n      +\n      +test_expect_success 'setup' '\n     -+\t> a.txt &&\n     -+\t> b.txt &&\n     -+\t> delete.txt &&\n     -+\tcat >expect1<<-\\EOF &&\n     -+\tM a.txt\n     -+\tH b.txt\n     -+\tH delete.txt\n     -+\tH expect1\n     -+\tH expect2\n     -+\tEOF\n     -+\tcat >expect2<<-EOF &&\n     -+\tC a.txt\n     -+\tR delete.txt\n     -+\tEOF\n     -+\tgit add a.txt b.txt delete.txt expect1 expect2 &&\n     ++\t>a.txt &&\n     ++\t>b.txt &&\n     ++\t>delete.txt &&\n     ++\tgit add a.txt b.txt delete.txt &&\n      +\tgit commit -m master:1 &&\n     -+\techo a>a.txt &&\n     -+\techo b>b.txt &&\n     ++\techo a >a.txt &&\n     ++\techo b >b.txt &&\n      +\techo delete >delete.txt &&\n      +\tgit add a.txt b.txt delete.txt &&\n      +\tgit commit -m master:2 &&\n      +\tgit checkout HEAD~ &&\n      +\tgit switch -c dev &&\n     ++\ttest_when_finished \"git switch master\" &&\n      +\techo change >a.txt &&\n      +\tgit add a.txt &&\n      +\tgit commit -m dev:1 &&\n      +\ttest_must_fail git merge master &&\n     -+\tgit ls-files -t --dedup >actual1 &&\n     -+\ttest_cmp expect1 actual1 &&\n     ++\tgit ls-files --deduplicate >actual &&\n     ++\tcat >expect <<-\\EOF &&\n     ++\ta.txt\n     ++\tb.txt\n     ++\tdelete.txt\n     ++\tEOF\n     ++\ttest_cmp expect actual &&\n      +\trm delete.txt &&\n     -+\tgit ls-files -d -m -t --dedup >actual2 &&\n     -+\ttest_cmp expect2 actual2\n     ++\tgit ls-files -d -m --deduplicate >actual &&\n     ++\tcat >expect <<-\\EOF &&\n     ++\ta.txt\n     ++\tdelete.txt\n     ++\tEOF\n     ++\ttest_cmp expect actual &&\n     ++\tgit ls-files -d -m -t  --deduplicate >actual &&\n     ++\tcat >expect <<-\\EOF &&\n     ++\tC a.txt\n     ++\tC a.txt\n     ++\tC a.txt\n     ++\tR delete.txt\n     ++\tC delete.txt\n     ++\tEOF\n     ++\ttest_cmp expect actual &&\n     ++\tgit ls-files -d -m -c  --deduplicate >actual &&\n     ++\tcat >expect <<-\\EOF &&\n     ++\ta.txt\n     ++\tb.txt\n     ++\tdelete.txt\n     ++\tEOF\n     ++\ttest_cmp expect actual &&\n     ++\tgit merge --abort\n      +'\n     -+\n      +test_done\n\n-- \ngitgitgadget\n"},{"id":"414538","messageId":"50efd9b45b1f5ea09af5268a081e1f131e0f2da4.1610856136.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v4.git.1610856136.gitgitgadget@gmail.com","subject":"[PATCH v4 2/3] ls_files.c: consolidate two for loops into one","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-17T04:02:15Z","receivedAt":"2021-01-17T04:03:20Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nRefactor the two for loops into one,skip showing the ce if it\nhas the same name as the previously shown one, only when doing so\nwon't lose information.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n builtin/ls-files.c | 68 +++++++++++++++++++---------------------------\n 1 file changed, 28 insertions(+), 40 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 6f97a23c2dc..49c242128d7 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -312,49 +312,37 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tif (show_killed)\n \t\t\tshow_killed_files(repo->index, dir);\n \t}\n-\tif (show_cached || show_stage) {\n-\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n-\n-\t\t\tconstruct_fullname(&fullname, repo, ce);\n-\n-\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (show_unmerged && !ce_stage(ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (ce->ce_flags & CE_UPDATE)\n-\t\t\t\tcontinue;\n-\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n-\t\t\t\tce_stage(ce) ? tag_unmerged :\n-\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n-\t\t\t\t tag_cached));\n-\t\t}\n-\t}\n-\tif (show_deleted || show_modified) {\n-\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n-\t\t\tstruct stat st;\n-\t\t\tint err;\n+\tif (! (show_cached || show_stage || show_deleted || show_modified))\n+\t\treturn;\n+\tfor (i = 0; i < repo->index->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = repo->index->cache[i];\n+\t\tstruct stat st;\n+\t\tint err;\n \n-\t\t\tconstruct_fullname(&fullname, repo, ce);\n+\t\tconstruct_fullname(&fullname, repo, ce);\n \n-\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (ce->ce_flags & CE_UPDATE)\n-\t\t\t\tcontinue;\n-\t\t\tif (ce_skip_worktree(ce))\n-\t\t\t\tcontinue;\n-\t\t\terr = lstat(fullname.buf, &st);\n-\t\t\tif (err) {\n-\t\t\t\t\tif (show_deleted)\n-\t\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\t\t\tif (show_modified)\n-\t\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n-\t\t\t}else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n+\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n+\t\t\tcontinue;\n+\t\tif (ce->ce_flags & CE_UPDATE)\n+\t\t\tcontinue;\n+\t\tif (show_cached || show_stage) {\n+\t\t\tif (!show_unmerged || ce_stage(ce))\n+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n+\t\t\t\t\tce_stage(ce) ? tag_unmerged :\n+\t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n+\t\t\t\t\t\ttag_cached));\n \t\t}\n+\t\tif (ce_skip_worktree(ce))\n+\t\t\tcontinue;\n+\t\terr = lstat(fullname.buf, &st);\n+\t\tif (err) {\n+\t\t\t\tif (show_deleted)\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\t\tif (show_modified)\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t}else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n+\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n \t}\n \n \tstrbuf_release(&fullname);\n-- \ngitgitgadget\n\n"},{"id":"414539","messageId":"0c7830d07db0aa1ec055b97de52bd873d05e3ab1.1610856136.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v4.git.1610856136.gitgitgadget@gmail.com","subject":"[PATCH v4 3/3] ls-files: add --deduplicate option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-17T04:02:16Z","receivedAt":"2021-01-17T04:03:20Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nIn order to provide users a better experience\nwhen viewing information about files in the index\nand the working tree, the `--deduplicate` option will suppress\nsome duplicate name under some conditions.\n\nIn a merge conflict, one file name of \"git ls-files\" output may\nappear multiple times. For example,now there is an unmerged path\n`a.c`,`a.c` will appear three times in the output of\n\"git ls-files\".We can use \"git ls-files --deduplicate\" to output\n`a.c` only one time.(unless `--stage` or `--unmerged` is\nused to view all the detailed information in the index)\n\nIn addition, if you use both `--delete` and `--modify` at\nthe same time, The `--deduplicate` option\ncan also suppress file name output.\n\nAdditional instructions:\nIn order to display entries information,`deduplicate` suppresses\nthe output of duplicate file names, not the output of duplicate\nentries information, so under the option of `-t`, `--stage`, `--unmerge`,\n`--deduplicate` will have no effect.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-ls-files.txt |  5 +++\n builtin/ls-files.c             | 23 +++++++++++++-\n t/t3012-ls-files-dedup.sh      | 57 ++++++++++++++++++++++++++++++++++\n 3 files changed, 84 insertions(+), 1 deletion(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\ndiff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\nindex cbcf5263dd0..d11c8ade402 100644\n--- a/Documentation/git-ls-files.txt\n+++ b/Documentation/git-ls-files.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t\t(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*\n \t\t(-[c|d|o|i|s|u|k|m])*\n \t\t[--eol]\n+\t\t[--deduplicate]\n \t\t[-x <pattern>|--exclude=<pattern>]\n \t\t[-X <file>|--exclude-from=<file>]\n \t\t[--exclude-per-directory=<file>]\n@@ -81,6 +82,10 @@ OPTIONS\n \t\\0 line termination on output and do not quote filenames.\n \tSee OUTPUT below for more information.\n \n+--deduplicate::\n+\tSuppress duplicate entries when there are unmerged paths in index\n+\tor `--deleted` and `--modified` are combined.\n+\n -x <pattern>::\n --exclude=<pattern>::\n \tSkip untracked files matching pattern.\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 49c242128d7..390d7ef6b44 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -35,6 +35,7 @@ static int line_terminator = '\\n';\n static int debug_mode;\n static int show_eol;\n static int recurse_submodules;\n+static int skipping_duplicates;\n \n static const char *prefix;\n static int max_prefix_len;\n@@ -301,6 +302,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n {\n \tint i;\n \tstruct strbuf fullname = STRBUF_INIT;\n+\tconst struct cache_entry *last_shown_ce;\n \n \t/* For cached/deleted files we don't need to even do the readdir */\n \tif (show_others || show_killed) {\n@@ -314,6 +316,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t}\n \tif (! (show_cached || show_stage || show_deleted || show_modified))\n \t\treturn;\n+\tlast_shown_ce = NULL;\n \tfor (i = 0; i < repo->index->cache_nr; i++) {\n \t\tconst struct cache_entry *ce = repo->index->cache[i];\n \t\tstruct stat st;\n@@ -321,28 +324,43 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \n \t\tconstruct_fullname(&fullname, repo, ce);\n \n+\t\tif (skipping_duplicates && last_shown_ce &&\n+\t\t\t!strcmp(last_shown_ce->name,ce->name))\n+\t\t\t\tcontinue;\n \t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n \t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n \t\t\tcontinue;\n \t\tif (ce->ce_flags & CE_UPDATE)\n \t\t\tcontinue;\n \t\tif (show_cached || show_stage) {\n+\t\t\tif (show_cached && skipping_duplicates && last_shown_ce &&\n+\t\t\t\t!strcmp(last_shown_ce->name,ce->name))\n+\t\t\t\t\tcontinue;\n \t\t\tif (!show_unmerged || ce_stage(ce))\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n \t\t\t\t\tce_stage(ce) ? tag_unmerged :\n \t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n \t\t\t\t\t\ttag_cached));\n+\t\t\tif(show_cached && skipping_duplicates)\n+\t\t\t\tlast_shown_ce = ce;\n \t\t}\n \t\tif (ce_skip_worktree(ce))\n \t\t\tcontinue;\n+\t\tif (skipping_duplicates && last_shown_ce && !strcmp(last_shown_ce->name,ce->name))\n+\t\t\tcontinue;\n \t\terr = lstat(fullname.buf, &st);\n \t\tif (err) {\n+\t\t\tif (skipping_duplicates && show_deleted && show_modified)\n+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\telse {\n \t\t\t\tif (show_deleted)\n \t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n \t\t\t\tif (show_modified)\n \t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n-\t\t}else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n+\t\t\t}\n+\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n \t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\tlast_shown_ce = ce;\n \t}\n \n \tstrbuf_release(&fullname);\n@@ -569,6 +587,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n+\t\tOPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n \t\tOPT_END()\n \t};\n \n@@ -600,6 +619,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\ttag_skip_worktree = \"S \";\n \t\ttag_resolve_undo = \"U \";\n \t}\n+\tif (show_tag && skipping_duplicates)\n+\t\tskipping_duplicates = 0;\n \tif (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)\n \t\trequire_work_tree = 1;\n \tif (show_unmerged)\ndiff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\nnew file mode 100755\nindex 00000000000..75877255c2c\n--- /dev/null\n+++ b/t/t3012-ls-files-dedup.sh\n@@ -0,0 +1,57 @@\n+#!/bin/sh\n+\n+test_description='git ls-files --deduplicate test'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\t>a.txt &&\n+\t>b.txt &&\n+\t>delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m master:1 &&\n+\techo a >a.txt &&\n+\techo b >b.txt &&\n+\techo delete >delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m master:2 &&\n+\tgit checkout HEAD~ &&\n+\tgit switch -c dev &&\n+\ttest_when_finished \"git switch master\" &&\n+\techo change >a.txt &&\n+\tgit add a.txt &&\n+\tgit commit -m dev:1 &&\n+\ttest_must_fail git merge master &&\n+\tgit ls-files --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\trm delete.txt &&\n+\tgit ls-files -d -m --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -t  --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tC a.txt\n+\tC a.txt\n+\tC a.txt\n+\tR delete.txt\n+\tC delete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -c  --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit merge --abort\n+'\n+test_done\n-- \ngitgitgadget\n"},{"id":"414541","messageId":"xmqqzh18mb4p.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"CAOLTT8RQWm-tpkj1aO1rPTCJApP5i+niQtNm_zMycSo5YT0B_w@mail.gmail.com","subject":"Re: [PATCH v3] ls-files.c: add --dedup option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-17T04:37:58Z","receivedAt":"2021-01-17T04:39:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"胡哲宁 <adlternative@gmail.com> writes:\n\n> Here I may disagree with your point of view:\n>                  if (errno != E_NOENT)\n>                          error_errno(\"cannot lstat '%s'\", fullname.buf);\n> With this sentence included, the patch will fail the test:\n> t/t3010-ls-files-killed-modified.sh.\n> the errno maybe ENOTDIR when you try to lstat a file`r` with `lstat(\"r/f\",&st);`\n> So I temporarily removed the judgment of errno.\n\nI didn't mean to give you a solution that is ready to be\ncut-and-pasted without thinking ;-) \n\nIf NOTDIR needs to also be excluded, then you can exclude it just\nlike the above illustration excludes NOENT to solve the issue,\nright?\n\n>>  #2: consolidate two for loops into one.\n>> ...\n>>      This changes the semantics.  The original iterates the index\n>>      twice, so you may see the same entry from --cached once and\n>>      then again from --modified.  The updated one still will show\n>>      the same entry twice but next to each other.\n>>\n> Well,This does change the semantics. I think people who used two\n> for loops before may want to separate different outputs.\n> Now, if you don’t use \"--deduplicate\", You may see six consecutive\n> items under a combination of multiple options.\n\nYes, and that is intended and is a vast improvement from the current\nbehaviour, which shows 3 in the first loop, bunch of unrelated\nentries from the rest of the first loop, yet another bunch of\nunrelated entries from the early part of the second loop and then\nfinally shows 3 from the second loop.  With the \"single loop\"\nupdate, at least, the entries are sorted by their path and it would\nmake it easier to see (if the user cares to trigger both --cached\nand --modified, that is), no?\n"},{"id":"414542","messageId":"CAPig+cQr2kMNbhZgYn3e1V79Y5QnCbDKYzQnriEdae4FomK8Aw@mail.gmail.com","threadId":"54943","inReplyTo":"CAOLTT8QNhz4PrMhAt9y58n+-bEjeOE+xst8rzcg-x045Amw7iw@mail.gmail.com","subject":"Re: [PATCH v3] ls-files.c: add --dedup option","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-01-17T05:11:41Z","receivedAt":"2021-01-17T05:12:58Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Jan 16, 2021 at 10:48 PM 胡哲宁 <adlternative@gmail.com> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> 于2021年1月16日周六 下午3:13写道：\n> > > +       git switch -c dev &&\n> >\n> > If someone adds a new test after this test, then that new test will\n> > run in the \"dev\" branch, which might be unexpected or undesirable. It\n> > often is a good idea to ensure that tests do certain types of cleanup\n> > to avoid breaking subsequent tests. Here, it would be a good idea to\n> > ensure that the test switches back to the original branch when it\n> > finishes (regardless of whether it finishes successfully or\n> > unsuccessfully).\n> >\n> >     git switch -c dev &&\n> >     test_when_finished \"git switch master\" &&\n> >\n> > Or you could use `git switch -` if you don't want to hard-code the\n> > name \"master\" in the test (since there has been effort lately to\n> > remove that name from tests.\n> >\n> I have little confuse about I can use` test_when_finished \"git switch master\" `,\n> but I can't use` test_when_finished \"git switch -\" `,\n> why？\n\nYou may use either one. I presented both as alternative approaches.\n"},{"id":"414543","messageId":"xmqqv9bwm6ar.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"f4d9af8a3124bcd37a35b1863441406f6d939f11.1610856136.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-17T06:22:20Z","receivedAt":"2021-01-17T06:23:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> This situation may occur in the original code: lstat() failed\n> but we use `&st` to feed ie_modified() later.\n>\n> It's buggy!\n\nWasteful extra paragraph with almost no information contents over\nwhat has already been said in the first paragraph.\n\n> Therefore, we can directly execute show_ce without the judgment of\n> ie_modified() when lstat() has failed.\n\nBut it introduces another bug, as you do not even see why a path\ncannot be lstat'ed, no?\n\n>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  builtin/ls-files.c | 9 ++++++---\n>  1 file changed, 6 insertions(+), 3 deletions(-)\n>\n> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> index c8eae899b82..6f97a23c2dc 100644\n> --- a/builtin/ls-files.c\n> +++ b/builtin/ls-files.c\n> @@ -347,9 +347,12 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>  \t\t\tif (ce_skip_worktree(ce))\n>  \t\t\t\tcontinue;\n>  \t\t\terr = lstat(fullname.buf, &st);\n> -\t\t\tif (show_deleted && err)\n> -\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n> -\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n> +\t\t\tif (err) {\n> +\t\t\t\t\tif (show_deleted)\n> +\t\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n> +\t\t\t\t\tif (show_modified)\n> +\t\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n> +\t\t\t}else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n>  \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n>  \t\t}\n>  \t}\n"},{"id":"414544","messageId":"xmqqr1mkm65c.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"0c7830d07db0aa1ec055b97de52bd873d05e3ab1.1610856136.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 3/3] ls-files: add --deduplicate option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-17T06:25:35Z","receivedAt":"2021-01-17T06:27:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> -\t\t}else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n> +\t\t\t}\n> +\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n\nThe preimage shows a style violation \"}else if\" introduced by an\nearlier step in the series, and this fixes it.\n\nPlease make sure to proofread your patches before you show to others\nto pretend that you are perfect coder and do not need \"oops what I\ndid earlier in the series was wrong and here is a fix-up\".\n\nThanks.\n"},{"id":"414563","messageId":"xmqqo8hnkvwo.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"CAPig+cQr2kMNbhZgYn3e1V79Y5QnCbDKYzQnriEdae4FomK8Aw@mail.gmail.com","subject":"Re: [PATCH v3] ls-files.c: add --dedup option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-17T23:04:23Z","receivedAt":"2021-01-17T23:05:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> On Sat, Jan 16, 2021 at 10:48 PM 胡哲宁 <adlternative@gmail.com> wrote:\n>> Eric Sunshine <sunshine@sunshineco.com> 于2021年1月16日周六 下午3:13写道：\n>> > > +       git switch -c dev &&\n>> >\n>> > If someone adds a new test after this test, then that new test will\n>> > run in the \"dev\" branch, which might be unexpected or undesirable. It\n>> > often is a good idea to ensure that tests do certain types of cleanup\n>> > to avoid breaking subsequent tests. Here, it would be a good idea to\n>> > ensure that the test switches back to the original branch when it\n>> > finishes (regardless of whether it finishes successfully or\n>> > unsuccessfully).\n>> >\n>> >     git switch -c dev &&\n>> >     test_when_finished \"git switch master\" &&\n>> >\n>> > Or you could use `git switch -` if you don't want to hard-code the\n>> > name \"master\" in the test (since there has been effort lately to\n>> > remove that name from tests.\n>> >\n>> I have little confuse about I can use` test_when_finished \"git switch master\" `,\n>> but I can't use` test_when_finished \"git switch -\" `,\n>> why？\n>\n> You may use either one. I presented both as alternative approaches.\n\nI am sensing a bit of miscommunication here.  You sound like you\nstill believe either would work OK, but to me, it sounds like that\nthe author claims the one of them does not work for him/her.\n\nI do not think the test framework itself mucks with the reflog to\nmake switching to \"-\" (or \"@{-1}\") break, so I find it implausible\nthat \"switch -\" form not to work, and unlike your \"either is OK\", I\nhave a moderately strong preference to use the \"go back to the\nprevious one, whatever it is called\" form.\n\nThanks.\n"},{"id":"414564","messageId":"xmqqbldnkuja.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"0c7830d07db0aa1ec055b97de52bd873d05e3ab1.1610856136.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 3/3] ls-files: add --deduplicate option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-17T23:34:01Z","receivedAt":"2021-01-17T23:35:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\n> new file mode 100755\n> index 00000000000..75877255c2c\n> --- /dev/null\n> +++ b/t/t3012-ls-files-dedup.sh\n> @@ -0,0 +1,57 @@\n> +#!/bin/sh\n> +\n> +test_description='git ls-files --deduplicate test'\n> +\n> +. ./test-lib.sh\n\nWe should already have a ls-files test so that we can add a handful\nnew tests to it, instead of dedicating a whole new test script.\n\nAlso, don't do everything in a single 'setup'.  There are various\nscenarios you want to make sure ls-files to work (grep for ls-files\nin the following you added---I count 4 of them), and when a future\ndeveloper touches the code, he or she may break one but not other\nthree.  The purpose you write tests is to protect your new feature\nfrom such a developer *AND* help such a developer to debug and fix\nhis or her changes.  For that, it would be a lot more sensible to\nhave one set-up that is common, and then four separate tests.\n\n> +test_expect_success 'setup' '\n> +\t>a.txt &&\n> +\t>b.txt &&\n> +\t>delete.txt &&\n> +\tgit add a.txt b.txt delete.txt &&\n> +\tgit commit -m master:1 &&\n\nNeedless use of the word \"master\".  Observe what is going on in the\nproject around you and avoid stepping other peoples' toes.  One of\nthe ongoing effort is to grep for the phrase master in t/ directory\nand examine what happens when the default initial branch name\nbecomes something other than 'master', so adding a needless hit like\nthis is most unwelcome.\n\n> +\techo a >a.txt &&\n> +\techo b >b.txt &&\n> +\techo delete >delete.txt &&\n> +\tgit add a.txt b.txt delete.txt &&\n> +\tgit commit -m master:2 &&\n\n> +\tgit checkout HEAD~ &&\n> +\tgit switch -c dev &&\n\nNeedless mixture of checkout/switch.  If you switch branches using\n\"git checkout\", for example, consistently do so, i.e.\n\n\tgit checkout -b dev HEAD~1 \n\nIt's not like these new tests are to test checkout and switch; your\nmission is to protect \"ls-files --dedup\" feature here.\n\n> +\ttest_when_finished \"git switch master\" &&\n> +\techo change >a.txt &&\n> +\tgit add a.txt &&\n> +\tgit commit -m dev:1 &&\n\nI'd consider all of the above to be 'setup' that is common for\nsubsequent tests.  It may make sense to actually do everything\non the initial branch, i.e. after creating two commits, do\n\n\tgit tag tip &&\n\tgit reset --hard HEAD^ &&\n\techo change >a.txt &&\n\tgit commit -a -m side &&\n\tgit tag side\n\nYou are always on the initial branch without ever switching, so\nthere is no need for the when_finished stuff.\n\nThen the first of your test is to show the index with conflicts.\n\n> +\ttest_must_fail git merge master &&\n\nThis will become \"git merge tip\" instead of 'master'.\n\n> +\tgit ls-files --deduplicate >actual &&\n> +\tcat >expect <<-\\EOF &&\n> +\ta.txt\n> +\tb.txt\n> +\tdelete.txt\n> +\tEOF\n> +\ttest_cmp expect actual &&\n\nAnd up to this point is the first test after 'setup'.\n\nThe next test should begin with:\n\n\tgit reset --hard side &&\n\ttest_must_fail git merge tip &&\n\nso that even when the first test is skipped, or left unmerged,\nyou'll begin with a known state.\n\n> +\trm delete.txt &&\n> +\tgit ls-files -d -m --deduplicate >actual &&\n> +\tcat >expect <<-\\EOF &&\n> +\ta.txt\n> +\tdelete.txt\n> +\tEOF\n> +\ttest_cmp expect actual &&\n> +\tgit ls-files -d -m -t  --deduplicate >actual &&\n> +\tcat >expect <<-\\EOF &&\n> +\tC a.txt\n> +\tC a.txt\n> +\tC a.txt\n> +\tR delete.txt\n> +\tC delete.txt\n> +\tEOF\n> +\ttest_cmp expect actual &&\n> +\tgit ls-files -d -m -c  --deduplicate >actual &&\n> +\tcat >expect <<-\\EOF &&\n> +\ta.txt\n> +\tb.txt\n> +\tdelete.txt\n> +\tEOF\n> +\ttest_cmp expect actual &&\n\nThese three can be kept in the same test_expect_success, as they are\nexercising read-only operation on the same state but with different\ndisplay options.\n\nBut in this case, the preparation is not too tedious (just a failed\nmerge plus a deletion), so you probably would prefer to split it\ninto 3 independent tests---that may make it more helpful to future\ndevelopers.\n\n> +\tgit merge --abort\n> +'\n> +test_done\n"},{"id":"414579","messageId":"CAOLTT8Syp2ZeTXW-m+e=dn2W773nScB_kwZLS3MjLTcFQ_bctw@mail.gmail.com","threadId":"54943","inReplyTo":"xmqqbldnkuja.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 3/3] ls-files: add --deduplicate option","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-18T04:09:51Z","receivedAt":"2021-01-18T04:09:10Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年1月18日周一 上午7:34写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > diff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\n> > new file mode 100755\n> > index 00000000000..75877255c2c\n> > --- /dev/null\n> > +++ b/t/t3012-ls-files-dedup.sh\n> > @@ -0,0 +1,57 @@\n> > +#!/bin/sh\n> > +\n> > +test_description='git ls-files --deduplicate test'\n> > +\n> > +. ./test-lib.sh\n>\n> We should already have a ls-files test so that we can add a handful\n> new tests to it, instead of dedicating a whole new test script.\n>\nFine,It might be easier for me to write a test file myself for the time being.\nBut I will learn slowly.\n> Also, don't do everything in a single 'setup'.  There are various\n> scenarios you want to make sure ls-files to work (grep for ls-files\n> in the following you added---I count 4 of them), and when a future\n> developer touches the code, he or she may break one but not other\n> three.  The purpose you write tests is to protect your new feature\n> from such a developer *AND* help such a developer to debug and fix\n> his or her changes.  For that, it would be a lot more sensible to\n> have one set-up that is common, and then four separate tests.\n>\n> > +test_expect_success 'setup' '\n> > +     >a.txt &&\n> > +     >b.txt &&\n> > +     >delete.txt &&\n> > +     git add a.txt b.txt delete.txt &&\n> > +     git commit -m master:1 &&\n>\n> Needless use of the word \"master\".  Observe what is going on in the\n> project around you and avoid stepping other peoples' toes.  One of\n> the ongoing effort is to grep for the phrase master in t/ directory\n> and examine what happens when the default initial branch name\n> becomes something other than 'master', so adding a needless hit like\n> this is most unwelcome.\n>\nWell, I will try my best to use less \"master\".\n> > +     echo a >a.txt &&\n> > +     echo b >b.txt &&\n> > +     echo delete >delete.txt &&\n> > +     git add a.txt b.txt delete.txt &&\n> > +     git commit -m master:2 &&\n>\n> > +     git checkout HEAD~ &&\n> > +     git switch -c dev &&\n>\n> Needless mixture of checkout/switch.  If you switch branches using\n> \"git checkout\", for example, consistently do so, i.e.\n>\n>         git checkout -b dev HEAD~1\n>\n> It's not like these new tests are to test checkout and switch; your\n> mission is to protect \"ls-files --dedup\" feature here.\n>\n> > +     test_when_finished \"git switch master\" &&\n> > +     echo change >a.txt &&\n> > +     git add a.txt &&\n> > +     git commit -m dev:1 &&\n>\n> I'd consider all of the above to be 'setup' that is common for\n> subsequent tests.  It may make sense to actually do everything\n> on the initial branch, i.e. after creating two commits, do\n>\nI understand it now...setup is for serve as a basis for other tests.\n>         git tag tip &&\n>         git reset --hard HEAD^ &&\n>         echo change >a.txt &&\n>         git commit -a -m side &&\n>         git tag side\n>\n> You are always on the initial branch without ever switching, so\n> there is no need for the when_finished stuff.\n>\n> Then the first of your test is to show the index with conflicts.\n>\n> > +     test_must_fail git merge master &&\n>\n> This will become \"git merge tip\" instead of 'master'.\n>\nuse tag instead of use branch name...\n> > +     git ls-files --deduplicate >actual &&\n> > +     cat >expect <<-\\EOF &&\n> > +     a.txt\n> > +     b.txt\n> > +     delete.txt\n> > +     EOF\n> > +     test_cmp expect actual &&\n>\n> And up to this point is the first test after 'setup'.\n>\n> The next test should begin with:\n>\n>         git reset --hard side &&\n>         test_must_fail git merge tip &&\n>\n> so that even when the first test is skipped, or left unmerged,\n> you'll begin with a known state.\n>\nWell,I understand now that the a test_success should allow other\nprogrammers to skip this test,so that we should reset to a known\nstate at the beginning of each test.\n> > +     rm delete.txt &&\n> > +     git ls-files -d -m --deduplicate >actual &&\n> > +     cat >expect <<-\\EOF &&\n> > +     a.txt\n> > +     delete.txt\n> > +     EOF\n> > +     test_cmp expect actual &&\n> > +     git ls-files -d -m -t  --deduplicate >actual &&\n> > +     cat >expect <<-\\EOF &&\n> > +     C a.txt\n> > +     C a.txt\n> > +     C a.txt\n> > +     R delete.txt\n> > +     C delete.txt\n> > +     EOF\n> > +     test_cmp expect actual &&\n> > +     git ls-files -d -m -c  --deduplicate >actual &&\n> > +     cat >expect <<-\\EOF &&\n> > +     a.txt\n> > +     b.txt\n> > +     delete.txt\n> > +     EOF\n> > +     test_cmp expect actual &&\n>\n> These three can be kept in the same test_expect_success, as they are\n> exercising read-only operation on the same state but with different\n> display options.\n>\nindeed so.\n> But in this case, the preparation is not too tedious (just a failed\n> merge plus a deletion), so you probably would prefer to split it\n> into 3 independent tests---that may make it more helpful to future\n> developers.\n>\nThanks:)\n> > +     git merge --abort\n> > +'\n> > +test_done\n"},{"id":"414583","messageId":"CAOLTT8QnhNqnJCgiqOZnd9cjxicAuk2Js1GKdHEJD_XK15UU+Q@mail.gmail.com","threadId":"54943","inReplyTo":"CAOLTT8Syp2ZeTXW-m+e=dn2W773nScB_kwZLS3MjLTcFQ_bctw@mail.gmail.com","subject":"Re: [PATCH v4 3/3] ls-files: add --deduplicate option","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-18T06:05:53Z","receivedAt":"2021-01-18T06:06:33Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Hi,Junio!\nHere I am thinking about the role of this \"--deduplicate\" is to\nsuppress duplicate filenames rather than duplicate entries. Do you\nthink I should modify this sentence?\n\n> > OPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n\n胡哲宁 <adlternative@gmail.com> 于2021年1月18日周一 下午12:09写道：\n>\n> Junio C Hamano <gitster@pobox.com> 于2021年1月18日周一 上午7:34写道：\n> >\n> > \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >\n> > > diff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\n> > > new file mode 100755\n> > > index 00000000000..75877255c2c\n> > > --- /dev/null\n> > > +++ b/t/t3012-ls-files-dedup.sh\n> > > @@ -0,0 +1,57 @@\n> > > +#!/bin/sh\n> > > +\n> > > +test_description='git ls-files --deduplicate test'\n> > > +\n> > > +. ./test-lib.sh\n> >\n> > We should already have a ls-files test so that we can add a handful\n> > new tests to it, instead of dedicating a whole new test script.\n> >\n> Fine,It might be easier for me to write a test file myself for the time being.\n> But I will learn slowly.\n> > Also, don't do everything in a single 'setup'.  There are various\n> > scenarios you want to make sure ls-files to work (grep for ls-files\n> > in the following you added---I count 4 of them), and when a future\n> > developer touches the code, he or she may break one but not other\n> > three.  The purpose you write tests is to protect your new feature\n> > from such a developer *AND* help such a developer to debug and fix\n> > his or her changes.  For that, it would be a lot more sensible to\n> > have one set-up that is common, and then four separate tests.\n> >\n> > > +test_expect_success 'setup' '\n> > > +     >a.txt &&\n> > > +     >b.txt &&\n> > > +     >delete.txt &&\n> > > +     git add a.txt b.txt delete.txt &&\n> > > +     git commit -m master:1 &&\n> >\n> > Needless use of the word \"master\".  Observe what is going on in the\n> > project around you and avoid stepping other peoples' toes.  One of\n> > the ongoing effort is to grep for the phrase master in t/ directory\n> > and examine what happens when the default initial branch name\n> > becomes something other than 'master', so adding a needless hit like\n> > this is most unwelcome.\n> >\n> Well, I will try my best to use less \"master\".\n> > > +     echo a >a.txt &&\n> > > +     echo b >b.txt &&\n> > > +     echo delete >delete.txt &&\n> > > +     git add a.txt b.txt delete.txt &&\n> > > +     git commit -m master:2 &&\n> >\n> > > +     git checkout HEAD~ &&\n> > > +     git switch -c dev &&\n> >\n> > Needless mixture of checkout/switch.  If you switch branches using\n> > \"git checkout\", for example, consistently do so, i.e.\n> >\n> >         git checkout -b dev HEAD~1\n> >\n> > It's not like these new tests are to test checkout and switch; your\n> > mission is to protect \"ls-files --dedup\" feature here.\n> >\n> > > +     test_when_finished \"git switch master\" &&\n> > > +     echo change >a.txt &&\n> > > +     git add a.txt &&\n> > > +     git commit -m dev:1 &&\n> >\n> > I'd consider all of the above to be 'setup' that is common for\n> > subsequent tests.  It may make sense to actually do everything\n> > on the initial branch, i.e. after creating two commits, do\n> >\n> I understand it now...setup is for serve as a basis for other tests.\n> >         git tag tip &&\n> >         git reset --hard HEAD^ &&\n> >         echo change >a.txt &&\n> >         git commit -a -m side &&\n> >         git tag side\n> >\n> > You are always on the initial branch without ever switching, so\n> > there is no need for the when_finished stuff.\n> >\n> > Then the first of your test is to show the index with conflicts.\n> >\n> > > +     test_must_fail git merge master &&\n> >\n> > This will become \"git merge tip\" instead of 'master'.\n> >\n> use tag instead of use branch name...\n> > > +     git ls-files --deduplicate >actual &&\n> > > +     cat >expect <<-\\EOF &&\n> > > +     a.txt\n> > > +     b.txt\n> > > +     delete.txt\n> > > +     EOF\n> > > +     test_cmp expect actual &&\n> >\n> > And up to this point is the first test after 'setup'.\n> >\n> > The next test should begin with:\n> >\n> >         git reset --hard side &&\n> >         test_must_fail git merge tip &&\n> >\n> > so that even when the first test is skipped, or left unmerged,\n> > you'll begin with a known state.\n> >\n> Well,I understand now that the a test_success should allow other\n> programmers to skip this test,so that we should reset to a known\n> state at the beginning of each test.\n> > > +     rm delete.txt &&\n> > > +     git ls-files -d -m --deduplicate >actual &&\n> > > +     cat >expect <<-\\EOF &&\n> > > +     a.txt\n> > > +     delete.txt\n> > > +     EOF\n> > > +     test_cmp expect actual &&\n> > > +     git ls-files -d -m -t  --deduplicate >actual &&\n> > > +     cat >expect <<-\\EOF &&\n> > > +     C a.txt\n> > > +     C a.txt\n> > > +     C a.txt\n> > > +     R delete.txt\n> > > +     C delete.txt\n> > > +     EOF\n> > > +     test_cmp expect actual &&\n> > > +     git ls-files -d -m -c  --deduplicate >actual &&\n> > > +     cat >expect <<-\\EOF &&\n> > > +     a.txt\n> > > +     b.txt\n> > > +     delete.txt\n> > > +     EOF\n> > > +     test_cmp expect actual &&\n> >\n> > These three can be kept in the same test_expect_success, as they are\n> > exercising read-only operation on the same state but with different\n> > display options.\n> >\n> indeed so.\n> > But in this case, the preparation is not too tedious (just a failed\n> > merge plus a deletion), so you probably would prefer to split it\n> > into 3 independent tests---that may make it more helpful to future\n> > developers.\n> >\n> Thanks:)\n> > > +     git merge --abort\n> > > +'\n> > > +test_done\n"},{"id":"414594","messageId":"CAPig+cRtpm=68GmHc_zjGaFX0q098-EpNfGH3AAk4uJfK7QH_w@mail.gmail.com","threadId":"54943","inReplyTo":"xmqqo8hnkvwo.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3] ls-files.c: add --dedup option","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-01-18T14:59:38Z","receivedAt":"2021-01-18T15:01:23Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, Jan 17, 2021 at 6:04 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> > On Sat, Jan 16, 2021 at 10:48 PM 胡哲宁 <adlternative@gmail.com> wrote:\n> >> Eric Sunshine <sunshine@sunshineco.com> 于2021年1月16日周六 下午3:13写道：\n> >> >     test_when_finished \"git switch master\" &&\n> >> >\n> >> > Or you could use `git switch -` if you don't want to hard-code the\n> >> > name \"master\" in the test (since there has been effort lately to\n> >> > remove that name from tests.\n> >> >\n> >> I have little confuse about I can use` test_when_finished \"git switch master\" `,\n> >> but I can't use` test_when_finished \"git switch -\" `,\n> >> why？\n> >\n> > You may use either one. I presented both as alternative approaches.\n>\n> I am sensing a bit of miscommunication here.  You sound like you\n> still believe either would work OK, but to me, it sounds like that\n> the author claims the one of them does not work for him/her.\n\nThat could be. My eye glided over the code too quickly to notice that\nit was using `git checkout HEAD~` followed immediately by `git switch\n-c dev`, which means that `git switch -` would indeed not return to\n\"master\" (in fact, it errors out).\n"},{"id":"414631","messageId":"xmqqczy2j5k0.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"CAOLTT8QnhNqnJCgiqOZnd9cjxicAuk2Js1GKdHEJD_XK15UU+Q@mail.gmail.com","subject":"Re: [PATCH v4 3/3] ls-files: add --deduplicate option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-18T21:31:11Z","receivedAt":"2021-01-18T21:32:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"胡哲宁 <adlternative@gmail.com> writes:\n\n> Here I am thinking about the role of this \"--deduplicate\" is to\n> suppress duplicate filenames rather than duplicate entries. Do you\n> think I should modify this sentence?\n>\n>> > OPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n\nI see no strong need to.  One set of output entries from \"ls-files\"\nmay say\n\n    $ git ls-files -u\n    100644 536e55524db72bd2acf175208aef4f3dfc148d41 1\tCOPYING\n    100644 536e55524db72bd2acf175208aef4f3dfc148d43 3\tCOPYING\n\nand these three \"entries\" are not duplicates.  Another set of output\nentries may say\n\n    $ git ls-files COPYING\n    COPYING\n    COPYING\n    COPYING\n\nand these output entries are duplicates.  If you deduplicate the\nlatter but not the former, then \"suppress duplicate entries\" is\nexactly what you are doing, I would think.\n\nAnd if you are asked to show entries that would look like this in a\nnot-deduplicated form:\n\n    $ git ls-files -u\n    100644 536e55524db72bd2acf175208aef4f3dfc148d41 1\tCOPYING\n    100644 536e55524db72bd2acf175208aef4f3dfc148d41 1\tCOPYING\n    100644 536e55524db72bd2acf175208aef4f3dfc148d43 3\tCOPYING\n\n\"suppressing duplicates\" would give us the first entry and drop the\nsecond entry that is identical to the second entry, I would think\n[*1*].\n\nSo \"duplicate entries\" would probably be more correct description of\nwhat we want to happen than \"duplicate filenames\".\n\n\n[Footnote]\n\n*1* Multiple \"common ancestor\" versions at stage #1 for the same\n    path is not an error.  That is how \"merge-resolve\" expresses\n    criss-cross merge where multiple merge-bases exist.\n\n    Multiple \"their\" versions at stage #3 for the same path is not\n    an error, and \"merge-octopus\" should use it to express contents\n    from histories being merged into ours, but the implementation of\n    the octopus strategy does not use this feature of the index.\n\n    Multiple \"our\" versions at stage #2 by definition should not\n    happen ;-)\n"},{"id":"414651","messageId":"CAOLTT8Qd81K7gjH7nq2g=z2j2YePntGzLfdzY3NJF0_nihdyMA@mail.gmail.com","threadId":"54943","inReplyTo":"xmqqczy2j5k0.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 3/3] ls-files: add --deduplicate option","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-19T02:56:27Z","receivedAt":"2021-01-19T02:55:56Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Thank you very much for your answer,I also learned a lot from it, then\nI will use the description of \"suppress duplicate entries\".\n\nJunio C Hamano <gitster@pobox.com> 于2021年1月19日周二 上午5:31写道：\n>\n> 胡哲宁 <adlternative@gmail.com> writes:\n>\n> > Here I am thinking about the role of this \"--deduplicate\" is to\n> > suppress duplicate filenames rather than duplicate entries. Do you\n> > think I should modify this sentence?\n> >\n> >> > OPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n>\n> I see no strong need to.  One set of output entries from \"ls-files\"\n> may say\n>\n>     $ git ls-files -u\n>     100644 536e55524db72bd2acf175208aef4f3dfc148d41 1   COPYING\n>     100644 536e55524db72bd2acf175208aef4f3dfc148d43 3   COPYING\n>\n> and these three \"entries\" are not duplicates.  Another set of output\n> entries may say\n>\n>     $ git ls-files COPYING\n>     COPYING\n>     COPYING\n>     COPYING\n>\n> and these output entries are duplicates.  If you deduplicate the\n> latter but not the former, then \"suppress duplicate entries\" is\n> exactly what you are doing, I would think.\n>\n> And if you are asked to show entries that would look like this in a\n> not-deduplicated form:\n>\n>     $ git ls-files -u\n>     100644 536e55524db72bd2acf175208aef4f3dfc148d41 1   COPYING\n>     100644 536e55524db72bd2acf175208aef4f3dfc148d41 1   COPYING\n>     100644 536e55524db72bd2acf175208aef4f3dfc148d43 3   COPYING\n>\n> \"suppressing duplicates\" would give us the first entry and drop the\n> second entry that is identical to the second entry, I would think\n> [*1*].\n>\n> So \"duplicate entries\" would probably be more correct description of\n> what we want to happen than \"duplicate filenames\".\n>\n>\n> [Footnote]\n>\n> *1* Multiple \"common ancestor\" versions at stage #1 for the same\n>     path is not an error.  That is how \"merge-resolve\" expresses\n>     criss-cross merge where multiple merge-bases exist.\n>\n>     Multiple \"their\" versions at stage #3 for the same path is not\n>     an error, and \"merge-octopus\" should use it to express contents\n>     from histories being merged into ours, but the implementation of\n>     the octopus strategy does not use this feature of the index.\n>\n>     Multiple \"our\" versions at stage #2 by definition should not\n>     happen ;-)\n"},{"id":"414655","messageId":"e9c5318670658b032ba921129859f9fb3b2ca017.1611037846.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v5.git.1611037846.gitgitgadget@gmail.com","subject":"[PATCH v5 3/3] ls-files.c: add --deduplicate option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-19T06:30:46Z","receivedAt":"2021-01-19T06:32:17Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nIn order to provide users a better experience\nwhen viewing information about files in the index\nand the working tree, the `--deduplicate` option will suppress\nsome duplicate name under some conditions.\n\nIn a merge conflict, one file name of \"git ls-files\" output may\nappear multiple times. For example,now there is an unmerged path\n`a.c`,`a.c` will appear three times in the output of\n\"git ls-files\".We can use \"git ls-files --deduplicate\" to output\n`a.c` only one time.(unless `--stage` or `--unmerged` is\nused to view all the detailed information in the index)\n\nIn addition, if you use both `--delete` and `--modify` at\nthe same time, The `--deduplicate` option\ncan also suppress file name output.\n\nAdditional instructions:\nIn order to display entries information,`deduplicate` suppresses\nthe output of duplicate file names, not the output of duplicate\nentries information, so under the option of `-t`, `--stage`, `--unmerge`,\n`--deduplicate` will have no effect.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-ls-files.txt |  5 +++\n builtin/ls-files.c             | 32 ++++++++++++++---\n t/t3012-ls-files-dedup.sh      | 66 ++++++++++++++++++++++++++++++++++\n 3 files changed, 98 insertions(+), 5 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\ndiff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\nindex cbcf5263dd0..d11c8ade402 100644\n--- a/Documentation/git-ls-files.txt\n+++ b/Documentation/git-ls-files.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t\t(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*\n \t\t(-[c|d|o|i|s|u|k|m])*\n \t\t[--eol]\n+\t\t[--deduplicate]\n \t\t[-x <pattern>|--exclude=<pattern>]\n \t\t[-X <file>|--exclude-from=<file>]\n \t\t[--exclude-per-directory=<file>]\n@@ -81,6 +82,10 @@ OPTIONS\n \t\\0 line termination on output and do not quote filenames.\n \tSee OUTPUT below for more information.\n \n+--deduplicate::\n+\tSuppress duplicate entries when there are unmerged paths in index\n+\tor `--deleted` and `--modified` are combined.\n+\n -x <pattern>::\n --exclude=<pattern>::\n \tSkip untracked files matching pattern.\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 1454ab1ae6f..709d727c574 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -35,6 +35,7 @@ static int line_terminator = '\\n';\n static int debug_mode;\n static int show_eol;\n static int recurse_submodules;\n+static int skipping_duplicates;\n \n static const char *prefix;\n static int max_prefix_len;\n@@ -301,6 +302,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n {\n \tint i;\n \tstruct strbuf fullname = STRBUF_INIT;\n+\tconst struct cache_entry *last_shown_ce;\n \n \t/* For cached/deleted files we don't need to even do the readdir */\n \tif (show_others || show_killed) {\n@@ -314,6 +316,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t}\n \tif (! (show_cached || show_stage || show_deleted || show_modified))\n \t\treturn;\n+\tlast_shown_ce = NULL;\n \tfor (i = 0; i < repo->index->cache_nr; i++) {\n \t\tconst struct cache_entry *ce = repo->index->cache[i];\n \t\tstruct stat st;\n@@ -321,30 +324,46 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \n \t\tconstruct_fullname(&fullname, repo, ce);\n \n+\t\tif (skipping_duplicates && last_shown_ce &&\n+\t\t\t!strcmp(last_shown_ce->name,ce->name))\n+\t\t\t\tcontinue;\n \t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n \t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n \t\t\tcontinue;\n \t\tif (ce->ce_flags & CE_UPDATE)\n \t\t\tcontinue;\n \t\tif (show_cached || show_stage) {\n+\t\t\tif (skipping_duplicates && last_shown_ce &&\n+\t\t\t\t!strcmp(last_shown_ce->name,ce->name))\n+\t\t\t\t\tcontinue;\n \t\t\tif (!show_unmerged || ce_stage(ce))\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n \t\t\t\t\tce_stage(ce) ? tag_unmerged :\n \t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n \t\t\t\t\t\ttag_cached));\n+\t\t\tif (show_cached && skipping_duplicates)\n+\t\t\t\tlast_shown_ce = ce;\n \t\t}\n \t\tif (ce_skip_worktree(ce))\n \t\t\tcontinue;\n+\t\tif (skipping_duplicates && last_shown_ce &&\n+\t\t\t!strcmp(last_shown_ce->name,ce->name))\n+\t\t\t\tcontinue;\n \t\terr = lstat(fullname.buf, &st);\n \t\tif (err) {\n-\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n-\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n-\t\t\tif (show_deleted)\n+\t\t\tif (skipping_duplicates && show_deleted && show_modified)\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified)\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\telse {\n+\t\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n+\t\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n+\t\t\t\tif (show_deleted)\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\t\tif (show_modified)\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\t}\n \t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n \t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\tlast_shown_ce = ce;\n \t}\n \n \tstrbuf_release(&fullname);\n@@ -571,6 +590,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n+\t\tOPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n \t\tOPT_END()\n \t};\n \n@@ -610,6 +630,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t * you also show the stage information.\n \t\t */\n \t\tshow_stage = 1;\n+\tif (show_tag || show_stage)\n+\t\tskipping_duplicates = 0;\n \tif (dir.exclude_per_dir)\n \t\texc_given = 1;\n \ndiff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\nnew file mode 100755\nindex 00000000000..2682b1f43a6\n--- /dev/null\n+++ b/t/t3012-ls-files-dedup.sh\n@@ -0,0 +1,66 @@\n+#!/bin/sh\n+\n+test_description='git ls-files --deduplicate test'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\t>a.txt &&\n+\t>b.txt &&\n+\t>delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m base &&\n+\techo a >a.txt &&\n+\techo b >b.txt &&\n+\techo delete >delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m tip &&\n+\tgit tag tip &&\n+\tgit reset --hard HEAD^ &&\n+\techo change >a.txt &&\n+\tgit commit -a -m side &&\n+\tgit tag side\n+'\n+\n+test_expect_success 'git ls-files --deduplicate to show unique unmerged path' '\n+\ttest_must_fail git merge tip &&\n+\tgit ls-files --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit merge --abort\n+'\n+\n+test_expect_success 'git ls-files -d -m --deduplicate with different display options' '\n+\tgit reset --hard side &&\n+\ttest_must_fail git merge tip &&\n+\trm delete.txt &&\n+\tgit ls-files -d -m --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -t --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tC a.txt\n+\tC a.txt\n+\tC a.txt\n+\tR delete.txt\n+\tC delete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -c --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit merge --abort\n+'\n+\n+test_done\n-- \ngitgitgadget\n"},{"id":"414656","messageId":"802ff802be884349b4a63c0ae6e4b783e6c7aedb.1611037846.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v5.git.1611037846.gitgitgadget@gmail.com","subject":"[PATCH v5 2/3] ls_files.c: consolidate two for loops into one","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-19T06:30:45Z","receivedAt":"2021-01-19T06:32:38Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nRefactor the two for loops into one,skip showing the ce if it\nhas the same name as the previously shown one, only when doing so\nwon't lose information.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n builtin/ls-files.c | 70 +++++++++++++++++++---------------------------\n 1 file changed, 29 insertions(+), 41 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex f1617260064..1454ab1ae6f 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -312,51 +312,39 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tif (show_killed)\n \t\t\tshow_killed_files(repo->index, dir);\n \t}\n-\tif (show_cached || show_stage) {\n-\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n+\tif (! (show_cached || show_stage || show_deleted || show_modified))\n+\t\treturn;\n+\tfor (i = 0; i < repo->index->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = repo->index->cache[i];\n+\t\tstruct stat st;\n+\t\tint err;\n \n-\t\t\tconstruct_fullname(&fullname, repo, ce);\n+\t\tconstruct_fullname(&fullname, repo, ce);\n \n-\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (show_unmerged && !ce_stage(ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (ce->ce_flags & CE_UPDATE)\n-\t\t\t\tcontinue;\n-\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n-\t\t\t\tce_stage(ce) ? tag_unmerged :\n-\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n-\t\t\t\t tag_cached));\n+\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n+\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n+\t\t\tcontinue;\n+\t\tif (ce->ce_flags & CE_UPDATE)\n+\t\t\tcontinue;\n+\t\tif (show_cached || show_stage) {\n+\t\t\tif (!show_unmerged || ce_stage(ce))\n+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n+\t\t\t\t\tce_stage(ce) ? tag_unmerged :\n+\t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n+\t\t\t\t\t\ttag_cached));\n \t\t}\n-\t}\n-\tif (show_deleted || show_modified) {\n-\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n-\t\t\tstruct stat st;\n-\t\t\tint err;\n-\n-\t\t\tconstruct_fullname(&fullname, repo, ce);\n-\n-\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (ce->ce_flags & CE_UPDATE)\n-\t\t\t\tcontinue;\n-\t\t\tif (ce_skip_worktree(ce))\n-\t\t\t\tcontinue;\n-\t\t\terr = lstat(fullname.buf, &st);\n-\t\t\tif (err) {\n-\t\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n-\t\t\t\t    error_errno(\"cannot lstat '%s'\", fullname.buf);\n-\t\t\t\tif (show_deleted)\n-\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\t\tif (show_modified)\n-\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n-\t\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n+\t\tif (ce_skip_worktree(ce))\n+\t\t\tcontinue;\n+\t\terr = lstat(fullname.buf, &st);\n+\t\tif (err) {\n+\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n+\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n+\t\t\tif (show_deleted)\n+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\tif (show_modified)\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n-\t\t}\n+\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n+\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n \t}\n \n \tstrbuf_release(&fullname);\n-- \ngitgitgadget\n\n"},{"id":"414657","messageId":"ec9464f6094f111bc7b6dc1dc07ecc9997d366d4.1611037846.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v5.git.1611037846.gitgitgadget@gmail.com","subject":"[PATCH v5 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-19T06:30:44Z","receivedAt":"2021-01-19T06:32:53Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThis situation may occur in the original code: lstat() failed\nbut we use `&st` to feed ie_modified() later.\n\nTherefore, we can directly execute show_ce without the judgment of\nie_modified() when lstat() has failed.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n builtin/ls-files.c | 11 ++++++++---\n 1 file changed, 8 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex c8eae899b82..f1617260064 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -347,9 +347,14 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tif (ce_skip_worktree(ce))\n \t\t\t\tcontinue;\n \t\t\terr = lstat(fullname.buf, &st);\n-\t\t\tif (show_deleted && err)\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n+\t\t\tif (err) {\n+\t\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n+\t\t\t\t    error_errno(\"cannot lstat '%s'\", fullname.buf);\n+\t\t\t\tif (show_deleted)\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\t\tif (show_modified)\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n \t\t}\n \t}\n-- \ngitgitgadget\n\n"},{"id":"414658","messageId":"pull.832.v5.git.1611037846.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v4.git.1610856136.gitgitgadget@gmail.com","subject":"[PATCH v5 0/3] builtin/ls-files.c:add git ls-file --dedup option","fromName":"阿德烈 via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-19T06:30:43Z","receivedAt":"2021-01-19T06:32:54Z","isPatch":true,"sender":{"key":"name:阿德烈","avatar":null},"body":"I am reading the source code of git ls-files and learned that git ls-files\nmay have duplicate files name when there are unmerged path in a branch merge\nor when different options are used at the same time. Users may fell confuse\nwhen they see these duplicate file names.\n\nAs Junio C Hamano said ,it have odd behaviour.\n\nTherefore, we can provide an additional option to git ls-files to delete\nthose repeated information.\n\nThis fixes https://github.com/gitgitgadget/git/issues/198\n\nThanks!\n\nZheNing Hu (3):\n  ls_files.c: bugfix for --deleted and --modified\n  ls_files.c: consolidate two for loops into one\n  ls-files.c: add --deduplicate option\n\n Documentation/git-ls-files.txt |  5 ++\n builtin/ls-files.c             | 83 ++++++++++++++++++++--------------\n t/t3012-ls-files-dedup.sh      | 66 +++++++++++++++++++++++++++\n 3 files changed, 120 insertions(+), 34 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\n\nbase-commit: 6d3ef5b467eccd2769f1aa1c555d317d3c8dc707\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-832%2Fadlternative%2Fls-files-dedup-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-832/adlternative/ls-files-dedup-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/832\n\nRange-diff vs v4:\n\n 1:  f4d9af8a312 ! 1:  ec9464f6094 ls_files.c: bugfix for --deleted and --modified\n     @@ Commit message\n          This situation may occur in the original code: lstat() failed\n          but we use `&st` to feed ie_modified() later.\n      \n     -    It's buggy!\n     -\n          Therefore, we can directly execute show_ce without the judgment of\n          ie_modified() when lstat() has failed.\n      \n     @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_s\n      -\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n      -\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n      +\t\t\tif (err) {\n     -+\t\t\t\t\tif (show_deleted)\n     -+\t\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     -+\t\t\t\t\tif (show_modified)\n     -+\t\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     -+\t\t\t}else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n     ++\t\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n     ++\t\t\t\t    error_errno(\"cannot lstat '%s'\", fullname.buf);\n     ++\t\t\t\tif (show_deleted)\n     ++\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     ++\t\t\t\tif (show_modified)\n     ++\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     ++\t\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n       \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n       \t\t}\n       \t}\n 2:  50efd9b45b1 ! 2:  802ff802be8 ls_files.c: consolidate two for loops into one\n     @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_s\n      -\tif (show_cached || show_stage) {\n      -\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n      -\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n     --\n     ++\tif (! (show_cached || show_stage || show_deleted || show_modified))\n     ++\t\treturn;\n     ++\tfor (i = 0; i < repo->index->cache_nr; i++) {\n     ++\t\tconst struct cache_entry *ce = repo->index->cache[i];\n     ++\t\tstruct stat st;\n     ++\t\tint err;\n     + \n      -\t\t\tconstruct_fullname(&fullname, repo, ce);\n     --\n     ++\t\tconstruct_fullname(&fullname, repo, ce);\n     + \n      -\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n      -\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n      -\t\t\t\tcontinue;\n     @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_s\n      -\t\t\t\tce_stage(ce) ? tag_unmerged :\n      -\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n      -\t\t\t\t tag_cached));\n     --\t\t}\n     ++\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n     ++\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n     ++\t\t\tcontinue;\n     ++\t\tif (ce->ce_flags & CE_UPDATE)\n     ++\t\t\tcontinue;\n     ++\t\tif (show_cached || show_stage) {\n     ++\t\t\tif (!show_unmerged || ce_stage(ce))\n     ++\t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n     ++\t\t\t\t\tce_stage(ce) ? tag_unmerged :\n     ++\t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n     ++\t\t\t\t\t\ttag_cached));\n     + \t\t}\n      -\t}\n      -\tif (show_deleted || show_modified) {\n      -\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n      -\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n      -\t\t\tstruct stat st;\n      -\t\t\tint err;\n     -+\tif (! (show_cached || show_stage || show_deleted || show_modified))\n     -+\t\treturn;\n     -+\tfor (i = 0; i < repo->index->cache_nr; i++) {\n     -+\t\tconst struct cache_entry *ce = repo->index->cache[i];\n     -+\t\tstruct stat st;\n     -+\t\tint err;\n     - \n     +-\n      -\t\t\tconstruct_fullname(&fullname, repo, ce);\n     -+\t\tconstruct_fullname(&fullname, repo, ce);\n     - \n     +-\n      -\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n      -\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n      -\t\t\t\tcontinue;\n     @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_s\n      -\t\t\t\tcontinue;\n      -\t\t\terr = lstat(fullname.buf, &st);\n      -\t\t\tif (err) {\n     --\t\t\t\t\tif (show_deleted)\n     --\t\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     --\t\t\t\t\tif (show_modified)\n     --\t\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     --\t\t\t}else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n     --\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     -+\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n     -+\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n     -+\t\t\tcontinue;\n     -+\t\tif (ce->ce_flags & CE_UPDATE)\n     -+\t\t\tcontinue;\n     -+\t\tif (show_cached || show_stage) {\n     -+\t\t\tif (!show_unmerged || ce_stage(ce))\n     -+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n     -+\t\t\t\t\tce_stage(ce) ? tag_unmerged :\n     -+\t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n     -+\t\t\t\t\t\ttag_cached));\n     - \t\t}\n     +-\t\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n     +-\t\t\t\t    error_errno(\"cannot lstat '%s'\", fullname.buf);\n     +-\t\t\t\tif (show_deleted)\n     +-\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     +-\t\t\t\tif (show_modified)\n     +-\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     +-\t\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n      +\t\tif (ce_skip_worktree(ce))\n      +\t\t\tcontinue;\n      +\t\terr = lstat(fullname.buf, &st);\n      +\t\tif (err) {\n     -+\t\t\t\tif (show_deleted)\n     -+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     -+\t\t\t\tif (show_modified)\n     -+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     -+\t\t}else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n     ++\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n     ++\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n     ++\t\t\tif (show_deleted)\n     ++\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     ++\t\t\tif (show_modified)\n     + \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     +-\t\t}\n     ++\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n      +\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n       \t}\n       \n 3:  0c7830d07db ! 3:  e9c53186706 ls-files: add --deduplicate option\n     @@ Metadata\n      Author: ZheNing Hu <adlternative@gmail.com>\n      \n       ## Commit message ##\n     -    ls-files: add --deduplicate option\n     +    ls-files.c: add --deduplicate option\n      \n          In order to provide users a better experience\n          when viewing information about files in the index\n     @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_s\n       \t\tif (ce->ce_flags & CE_UPDATE)\n       \t\t\tcontinue;\n       \t\tif (show_cached || show_stage) {\n     -+\t\t\tif (show_cached && skipping_duplicates && last_shown_ce &&\n     ++\t\t\tif (skipping_duplicates && last_shown_ce &&\n      +\t\t\t\t!strcmp(last_shown_ce->name,ce->name))\n      +\t\t\t\t\tcontinue;\n       \t\t\tif (!show_unmerged || ce_stage(ce))\n     @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_s\n       \t\t\t\t\tce_stage(ce) ? tag_unmerged :\n       \t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n       \t\t\t\t\t\ttag_cached));\n     -+\t\t\tif(show_cached && skipping_duplicates)\n     ++\t\t\tif (show_cached && skipping_duplicates)\n      +\t\t\t\tlast_shown_ce = ce;\n       \t\t}\n       \t\tif (ce_skip_worktree(ce))\n       \t\t\tcontinue;\n     -+\t\tif (skipping_duplicates && last_shown_ce && !strcmp(last_shown_ce->name,ce->name))\n     -+\t\t\tcontinue;\n     ++\t\tif (skipping_duplicates && last_shown_ce &&\n     ++\t\t\t!strcmp(last_shown_ce->name,ce->name))\n     ++\t\t\t\tcontinue;\n       \t\terr = lstat(fullname.buf, &st);\n       \t\tif (err) {\n     +-\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n     +-\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n     +-\t\t\tif (show_deleted)\n      +\t\t\tif (skipping_duplicates && show_deleted && show_modified)\n     -+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     + \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     +-\t\t\tif (show_modified)\n     +-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n      +\t\t\telse {\n     - \t\t\t\tif (show_deleted)\n     - \t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     - \t\t\t\tif (show_modified)\n     - \t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     --\t\t}else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n     ++\t\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n     ++\t\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n     ++\t\t\t\tif (show_deleted)\n     ++\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     ++\t\t\t\tif (show_modified)\n     ++\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n      +\t\t\t}\n     -+\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n     + \t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n       \t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n      +\t\tlast_shown_ce = ce;\n       \t}\n     @@ builtin/ls-files.c: int cmd_ls_files(int argc, const char **argv, const char *cm\n       \t};\n       \n      @@ builtin/ls-files.c: int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n     - \t\ttag_skip_worktree = \"S \";\n     - \t\ttag_resolve_undo = \"U \";\n     - \t}\n     -+\tif (show_tag && skipping_duplicates)\n     + \t\t * you also show the stage information.\n     + \t\t */\n     + \t\tshow_stage = 1;\n     ++\tif (show_tag || show_stage)\n      +\t\tskipping_duplicates = 0;\n     - \tif (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)\n     - \t\trequire_work_tree = 1;\n     - \tif (show_unmerged)\n     + \tif (dir.exclude_per_dir)\n     + \t\texc_given = 1;\n     + \n      \n       ## t/t3012-ls-files-dedup.sh (new) ##\n      @@\n     @@ t/t3012-ls-files-dedup.sh (new)\n      +\t>b.txt &&\n      +\t>delete.txt &&\n      +\tgit add a.txt b.txt delete.txt &&\n     -+\tgit commit -m master:1 &&\n     ++\tgit commit -m base &&\n      +\techo a >a.txt &&\n      +\techo b >b.txt &&\n      +\techo delete >delete.txt &&\n      +\tgit add a.txt b.txt delete.txt &&\n     -+\tgit commit -m master:2 &&\n     -+\tgit checkout HEAD~ &&\n     -+\tgit switch -c dev &&\n     -+\ttest_when_finished \"git switch master\" &&\n     ++\tgit commit -m tip &&\n     ++\tgit tag tip &&\n     ++\tgit reset --hard HEAD^ &&\n      +\techo change >a.txt &&\n     -+\tgit add a.txt &&\n     -+\tgit commit -m dev:1 &&\n     -+\ttest_must_fail git merge master &&\n     ++\tgit commit -a -m side &&\n     ++\tgit tag side\n     ++'\n     ++\n     ++test_expect_success 'git ls-files --deduplicate to show unique unmerged path' '\n     ++\ttest_must_fail git merge tip &&\n      +\tgit ls-files --deduplicate >actual &&\n      +\tcat >expect <<-\\EOF &&\n      +\ta.txt\n     @@ t/t3012-ls-files-dedup.sh (new)\n      +\tdelete.txt\n      +\tEOF\n      +\ttest_cmp expect actual &&\n     ++\tgit merge --abort\n     ++'\n     ++\n     ++test_expect_success 'git ls-files -d -m --deduplicate with different display options' '\n     ++\tgit reset --hard side &&\n     ++\ttest_must_fail git merge tip &&\n      +\trm delete.txt &&\n      +\tgit ls-files -d -m --deduplicate >actual &&\n      +\tcat >expect <<-\\EOF &&\n     @@ t/t3012-ls-files-dedup.sh (new)\n      +\tdelete.txt\n      +\tEOF\n      +\ttest_cmp expect actual &&\n     -+\tgit ls-files -d -m -t  --deduplicate >actual &&\n     ++\tgit ls-files -d -m -t --deduplicate >actual &&\n      +\tcat >expect <<-\\EOF &&\n      +\tC a.txt\n      +\tC a.txt\n     @@ t/t3012-ls-files-dedup.sh (new)\n      +\tC delete.txt\n      +\tEOF\n      +\ttest_cmp expect actual &&\n     -+\tgit ls-files -d -m -c  --deduplicate >actual &&\n     ++\tgit ls-files -d -m -c --deduplicate >actual &&\n      +\tcat >expect <<-\\EOF &&\n      +\ta.txt\n      +\tb.txt\n     @@ t/t3012-ls-files-dedup.sh (new)\n      +\ttest_cmp expect actual &&\n      +\tgit merge --abort\n      +'\n     ++\n      +test_done\n\n-- \ngitgitgadget\n"},{"id":"414859","messageId":"xmqqczxzfj5v.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"802ff802be884349b4a63c0ae6e4b783e6c7aedb.1611037846.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 2/3] ls_files.c: consolidate two for loops into one","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-20T20:27:40Z","receivedAt":"2021-01-20T20:37:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Refactor the two for loops into one,skip showing the ce if it\n> has the same name as the previously shown one, only when doing so\n> won't lose information.\n>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  builtin/ls-files.c | 70 +++++++++++++++++++---------------------------\n>  1 file changed, 29 insertions(+), 41 deletions(-)\n\nThis one needs a bit more work, but I like the basic structure of\nthe rewritten loop.\n\n> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> index f1617260064..1454ab1ae6f 100644\n> --- a/builtin/ls-files.c\n> +++ b/builtin/ls-files.c\n> @@ -312,51 +312,39 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>  \t\tif (show_killed)\n>  \t\t\tshow_killed_files(repo->index, dir);\n>  \t}\n> -\tif (show_cached || show_stage) {\n> -\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n> -\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n> +\tif (! (show_cached || show_stage || show_deleted || show_modified))\n> +\t\treturn;\n\nIf none of these four are given, nothing will be given after this\npoint, so returning early is good.\n\n> +\tfor (i = 0; i < repo->index->cache_nr; i++) {\n> +\t\tconst struct cache_entry *ce = repo->index->cache[i];\n> +\t\tstruct stat st;\n> +\t\tint err;\n>  \n> +\t\tconstruct_fullname(&fullname, repo, ce);\n>  \n> +\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n> +\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n> +\t\t\tcontinue;\n> +\t\tif (ce->ce_flags & CE_UPDATE)\n> +\t\t\tcontinue;\n\nThe above two are common between the original two codepaths, and\nmerging them is good.\n\n> +\t\tif (show_cached || show_stage) {\n> +\t\t\tif (!show_unmerged || ce_stage(ce))\n> +\t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n> +\t\t\t\t\tce_stage(ce) ? tag_unmerged :\n> +\t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n> +\t\t\t\t\t\ttag_cached));\n>  \t\t}\n\nWe would want to reduce the indentation level of the show_ce() by\nconsolidating the nested if/if to\n\n\t\tif ((show_cached || show_stage) &&\n                    (!show_unmerged || ce_stage(ce)))\n\t\t\tshow_ce(...);\n\n\nEverything below from this point should be skipped (especially, the\ncall to lstat()) unless show_modified and/or show_deleted was asked\nby the caller, i.e.  we want to insert\n\n\t\tif (!(show_deleted || show_modified))\n\t\t\tcontinue;\n\nhere, before we call ce_skip_worktree(), I think.\n\n> +\t\tif (ce_skip_worktree(ce))\n> +\t\t\tcontinue;\n> +\t\terr = lstat(fullname.buf, &st);\n> +\t\tif (err) {\n> +\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n> +\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n> +\t\t\tif (show_deleted)\n> +\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n> +\t\t\tif (show_modified)\n>  \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n> -\t\t}\n> +\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n> +\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n>  \t}\n\nAnd this part would look somewhat different if we take my earlier\nsuggestion for [1/3].\n\nThanks.\n"},{"id":"414860","messageId":"xmqqlfcnfj82.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"ec9464f6094f111bc7b6dc1dc07ecc9997d366d4.1611037846.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-20T20:26:21Z","receivedAt":"2021-01-20T20:37:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> This situation may occur in the original code: lstat() failed\n> but we use `&st` to feed ie_modified() later.\n>\n> Therefore, we can directly execute show_ce without the judgment of\n> ie_modified() when lstat() has failed.\n>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n\nThanks.  A few comments:\n\n * The error_errno() line is not indented correctly; I'll fix it up\n   while queuing, but it would conflict with 2/3 as you'll be moving\n   that line around.\n\n * When we say \"error\", we do not even know if the thing got removed\n   or modified at all, so it is somewhat strange to report it as\n   such (the path may be intact and the only issue may be that we\n   cannot read the containing directory).  It is equally strange not\n   to say anything on the path, and between the two, there isn't\n   clearly a more correct answer.  What you implemented here does\n   not change the traditional behaviour of reporting it as\n   deleted/modified to \"alert\" the user, which I think is good.\n\n * The logic for modified entry looks a bit duplicated.  I wonder if\n   the one at the end of this message reads better.  Renaming err to\n   stat_err is optional, but I think the name makes it clear why it\n   is sensible that these two places use the variable as a sign that\n   the path was deleted and/or modified.\n\n>  \t\t\terr = lstat(fullname.buf, &st);\n> +\t\t\tif (err) {\n> +\t\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n> +\t\t\t\t    error_errno(\"cannot lstat '%s'\", fullname.buf);\n> +\t\t\t\tif (show_deleted)\n> +\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n> +\t\t\t\tif (show_modified)\n> +\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n> +\t\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n>  \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n\n\n\t\t\tstat_err = lstat(...);\n\t\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n\n\t\t\tif (show_deleted && stat_err)\n\t\t\t\tshow_ce(..., tag_removed);\n\t\t\tif (show_modified &&\n\t\t\t    (stat_err || ie_modified(..., &st, 0)))\n\t\t\t\tshow_ce(..., tag_modified);\n\n"},{"id":"414862","messageId":"xmqq7do7fggn.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"e9c5318670658b032ba921129859f9fb3b2ca017.1611037846.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 3/3] ls-files.c: add --deduplicate option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-20T21:26:00Z","receivedAt":"2021-01-20T21:38:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> @@ -321,30 +324,46 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>  \n>  \t\tconstruct_fullname(&fullname, repo, ce);\n>  \n> +\t\tif (skipping_duplicates && last_shown_ce &&\n> +\t\t\t!strcmp(last_shown_ce->name,ce->name))\n> +\t\t\t\tcontinue;\n\nStyle.  Missing SP after comma.\n\n>  \t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n>  \t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n>  \t\t\tcontinue;\n>  \t\tif (ce->ce_flags & CE_UPDATE)\n>  \t\t\tcontinue;\n>  \t\tif (show_cached || show_stage) {\n> +\t\t\tif (skipping_duplicates && last_shown_ce &&\n> +\t\t\t\t!strcmp(last_shown_ce->name,ce->name))\n> +\t\t\t\t\tcontinue;\n\nOK.  When show_stage is set, skipping_duplicates is automatically\nturned off (and show_unmerged is automatically covered as it turns\nshow_stage on automatically).  So this feature has really become\n\"are we showing only names, and if so, did we show an entry of the\nsame name before?\".\n\n>  \t\t\tif (!show_unmerged || ce_stage(ce))\n>  \t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n>  \t\t\t\t\tce_stage(ce) ? tag_unmerged :\n>  \t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n>  \t\t\t\t\t\ttag_cached));\n> +\t\t\tif (show_cached && skipping_duplicates)\n> +\t\t\t\tlast_shown_ce = ce;\n\nThe code that calls show_ce() belonging to a totally separate if()\nstatement makes my stomach hurt---how are we going to guarantee that\n\"last shown\" really will keep track of what was shown last?\n\nShouldn't the above be more like this?\n\n- \t\t\tif (!show_unmerged || ce_stage(ce))\n+ \t\t\tif (!show_unmerged || ce_stage(ce)) {\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n \t\t\t\t\tce_stage(ce) ? tag_unmerged :\n \t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n \t\t\t\t\t\ttag_cached));\n+\t\t\t\tlast_shown_ce = ce;\n+\t\t\t}\n\nIt does maintain last_shown_ce even when skipping_duplicates is not\nset, but I think that is overall win.  Assigning unconditionally\nwould be cheaper than making a conditional jump on the variable and\nmake assignment (or not).\n\n>  \t\t}\n>  \t\tif (ce_skip_worktree(ce))\n>  \t\t\tcontinue;\n> +\t\tif (skipping_duplicates && last_shown_ce &&\n> +\t\t\t!strcmp(last_shown_ce->name,ce->name))\n> +\t\t\t\tcontinue;\n\nStyle.  Missing SP after comma.\n\nOK, if we've shown an entry of the same name under skip-duplicates\nmode, and the code that follows will show the same entry (if they\ndecide to show it), so we can go to the next entry early.\n\n>  \t\terr = lstat(fullname.buf, &st);\n>  \t\tif (err) {\n> -\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n> -\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n> -\t\t\tif (show_deleted)\n> +\t\t\tif (skipping_duplicates && show_deleted && show_modified)\n>  \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n> -\t\t\tif (show_modified)\n> -\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n> +\t\t\telse {\n> +\t\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n> +\t\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n> +\t\t\t\tif (show_deleted)\n> +\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n> +\t\t\t\tif (show_modified)\n> +\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n> +\t\t\t}\n>  \t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n>  \t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n\nThis part will change shape quite a bit when we follow the\nsuggestion I made on 1/3, so I won't analyze how correct this\nversion is.\n\n> +\t\tlast_shown_ce = ce;\n>  \t}\n>  \n>  \tstrbuf_release(&fullname);\n> @@ -571,6 +590,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n>  \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n>  \t\tOPT__ABBREV(&abbrev),\n>  \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n> +\t\tOPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n>  \t\tOPT_END()\n>  \t};\n>  \n> @@ -610,6 +630,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n>  \t\t * you also show the stage information.\n>  \t\t */\n>  \t\tshow_stage = 1;\n> +\tif (show_tag || show_stage)\n> +\t\tskipping_duplicates = 0;\n\nOK.\n\n>  \tif (dir.exclude_per_dir)\n>  \t\texc_given = 1;\n>  \n\nThanks.\n"},{"id":"414893","messageId":"CAOLTT8QN=qLUb7HSCeufbo_zQyhjgQXE4T6x_XCg9Y8t_kT2Ag@mail.gmail.com","threadId":"54943","inReplyTo":"xmqqlfcnfj82.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-21T10:02:26Z","receivedAt":"2021-01-21T10:02:01Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年1月21日周四 上午4:26写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > This situation may occur in the original code: lstat() failed\n> > but we use `&st` to feed ie_modified() later.\n> >\n> > Therefore, we can directly execute show_ce without the judgment of\n> > ie_modified() when lstat() has failed.\n> >\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > ---\n>\n> Thanks.  A few comments:\n>\n>  * The error_errno() line is not indented correctly; I'll fix it up\n>    while queuing, but it would conflict with 2/3 as you'll be moving\n>    that line around.\n>\nI might not have noticed,very Sorry.\n>  * When we say \"error\", we do not even know if the thing got removed\n>    or modified at all, so it is somewhat strange to report it as\n>    such (the path may be intact and the only issue may be that we\n>    cannot read the containing directory).  It is equally strange not\n>    to say anything on the path, and between the two, there isn't\n>    clearly a more correct answer.  What you implemented here does\n>    not change the traditional behaviour of reporting it as\n>    deleted/modified to \"alert\" the user, which I think is good.\n>\nHaha,thanks!\n>  * The logic for modified entry looks a bit duplicated.  I wonder if\n>    the one at the end of this message reads better.  Renaming err to\n>    stat_err is optional, but I think the name makes it clear why it\n>    is sensible that these two places use the variable as a sign that\n>    the path was deleted and/or modified.\n>\n> >                       err = lstat(fullname.buf, &st);\n> > +                     if (err) {\n> > +                             if (errno != ENOENT && errno != ENOTDIR)\n> > +                                 error_errno(\"cannot lstat '%s'\", fullname.buf);\n> > +                             if (show_deleted)\n> > +                                     show_ce(repo, dir, ce, fullname.buf, tag_removed);\n> > +                             if (show_modified)\n> > +                                     show_ce(repo, dir, ce, fullname.buf, tag_modified);\n> > +                     } else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n> >                               show_ce(repo, dir, ce, fullname.buf, tag_modified);\n>\n>\n>                         stat_err = lstat(...);\n>                         if (stat_err && (errno != ENOENT && errno != ENOTDIR))\n>                                 error_errno(\"cannot lstat '%s'\", fullname.buf);\n>\n>                         if (show_deleted && stat_err)\n>                                 show_ce(..., tag_removed);\n>                         if (show_modified &&\n>                             (stat_err || ie_modified(..., &st, 0)))\n>                                 show_ce(..., tag_modified);\n>\nYes,\"stat_err\" may better then \"err\".\nAnd putting the conditions together will make the code more compact.\n\nThanks for your comment:)\n"},{"id":"414895","messageId":"CAOLTT8R=fF00WCVBSTDKHG_3p5RcZaxM2AU-cUj1sNWvy=mhCQ@mail.gmail.com","threadId":"54943","inReplyTo":"xmqq7do7fggn.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 3/3] ls-files.c: add --deduplicate option","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-21T11:00:42Z","receivedAt":"2021-01-21T11:01:48Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年1月21日周四 上午5:26写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > @@ -321,30 +324,46 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n> >\n> >               construct_fullname(&fullname, repo, ce);\n> >\n> > +             if (skipping_duplicates && last_shown_ce &&\n> > +                     !strcmp(last_shown_ce->name,ce->name))\n> > +                             continue;\n>\n> Style.  Missing SP after comma.\nGet it.\n>\n> >               if ((dir->flags & DIR_SHOW_IGNORED) &&\n> >                       !ce_excluded(dir, repo->index, fullname.buf, ce))\n> >                       continue;\n> >               if (ce->ce_flags & CE_UPDATE)\n> >                       continue;\n> >               if (show_cached || show_stage) {\n> > +                     if (skipping_duplicates && last_shown_ce &&\n> > +                             !strcmp(last_shown_ce->name,ce->name))\n> > +                                     continue;\n>\n> OK.  When show_stage is set, skipping_duplicates is automatically\n> turned off (and show_unmerged is automatically covered as it turns\n> show_stage on automatically).  So this feature has really become\n> \"are we showing only names, and if so, did we show an entry of the\n> same name before?\".\nYeah,showing only names,so I yesterday ask such question :)\n>\n> >                       if (!show_unmerged || ce_stage(ce))\n> >                               show_ce(repo, dir, ce, fullname.buf,\n> >                                       ce_stage(ce) ? tag_unmerged :\n> >                                       (ce_skip_worktree(ce) ? tag_skip_worktree :\n> >                                               tag_cached));\n> > +                     if (show_cached && skipping_duplicates)\n> > +                             last_shown_ce = ce;\n>\n> The code that calls show_ce() belonging to a totally separate if()\n> statement makes my stomach hurt---how are we going to guarantee that\n> \"last shown\" really will keep track of what was shown last?\n>\n> Shouldn't the above be more like this?\n>\n> -                       if (!show_unmerged || ce_stage(ce))\n> +                       if (!show_unmerged || ce_stage(ce)) {\n>                                 show_ce(repo, dir, ce, fullname.buf,\n>                                         ce_stage(ce) ? tag_unmerged :\n>                                         (ce_skip_worktree(ce) ? tag_skip_worktree :\n>                                                 tag_cached));\n> +                               last_shown_ce = ce;\n> +                       }\n>\nwell,I am also thinking about this question :\"last_shown_ce\" is not true\nlast shown ce,but may be If \"last_shown_ce\" truly seen every last shown\nce ,We may need more cumbersome logic to make the program correct.\nI have tried the processing method of your above code before, but found\n that some errors may have occurred.\n> It does maintain last_shown_ce even when skipping_duplicates is not\n> set, but I think that is overall win.  Assigning unconditionally\n> would be cheaper than making a conditional jump on the variable and\n> make assignment (or not).\n>\n> >               }\n> >               if (ce_skip_worktree(ce))\n> >                       continue;\n> > +             if (skipping_duplicates && last_shown_ce &&\n> > +                     !strcmp(last_shown_ce->name,ce->name))\n> > +                             continue;\n>\n> Style.  Missing SP after comma.\n>\n> OK, if we've shown an entry of the same name under skip-duplicates\n> mode, and the code that follows will show the same entry (if they\n> decide to show it), so we can go to the next entry early.\n>\n> >               err = lstat(fullname.buf, &st);\n> >               if (err) {\n> > -                     if (errno != ENOENT && errno != ENOTDIR)\n> > -                             error_errno(\"cannot lstat '%s'\", fullname.buf);\n> > -                     if (show_deleted)\n> > +                     if (skipping_duplicates && show_deleted && show_modified)\n> >                               show_ce(repo, dir, ce, fullname.buf, tag_removed);\n> > -                     if (show_modified)\n> > -                             show_ce(repo, dir, ce, fullname.buf, tag_modified);\n> > +                     else {\n> > +                             if (errno != ENOENT && errno != ENOTDIR)\n> > +                                     error_errno(\"cannot lstat '%s'\", fullname.buf);\n> > +                             if (show_deleted)\n> > +                                     show_ce(repo, dir, ce, fullname.buf, tag_removed);\n> > +                             if (show_modified)\n> > +                                     show_ce(repo, dir, ce, fullname.buf, tag_modified);\n> > +                     }\n> >               } else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n> >                       show_ce(repo, dir, ce, fullname.buf, tag_modified);\n>\n> This part will change shape quite a bit when we follow the\n> suggestion I made on 1/3, so I won't analyze how correct this\n> version is.\n>\nFine...\n> > +             last_shown_ce = ce;\n> >       }\n> >\n> >       strbuf_release(&fullname);\n> > @@ -571,6 +590,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n> >                       N_(\"pretend that paths removed since <tree-ish> are still present\")),\n> >               OPT__ABBREV(&abbrev),\n> >               OPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n> > +             OPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n> >               OPT_END()\n> >       };\n> >\n> > @@ -610,6 +630,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n> >                * you also show the stage information.\n> >                */\n> >               show_stage = 1;\n> > +     if (show_tag || show_stage)\n> > +             skipping_duplicates = 0;\n>\n> OK.\n>\n> >       if (dir.exclude_per_dir)\n> >               exc_given = 1;\n> >\n>\n> Thanks.\n\nThanks,Junio,I find my PR in gitgitgadget have been accepted.\nBy the way,\nI found the problem \"leftoverbit\" and \"good first issue\" on gitgitgadget\nIt may not have been updated for a long time, and most of the above\nmay have been resolved.\n\nShould it do an update?\nThen we can happily be a \"bounty hunter\" in the git community, haha!\n"},{"id":"414896","messageId":"CAOLTT8Rv76ivN5HcocYmqak0c5+Dezm7F35uoLWKrHYp0NobTw@mail.gmail.com","threadId":"54943","inReplyTo":"xmqqczxzfj5v.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 2/3] ls_files.c: consolidate two for loops into one","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-21T11:05:57Z","receivedAt":"2021-01-21T11:06:27Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年1月21日周四 上午4:27写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > Refactor the two for loops into one,skip showing the ce if it\n> > has the same name as the previously shown one, only when doing so\n> > won't lose information.\n> >\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > ---\n> >  builtin/ls-files.c | 70 +++++++++++++++++++---------------------------\n> >  1 file changed, 29 insertions(+), 41 deletions(-)\n>\n> This one needs a bit more work, but I like the basic structure of\n> the rewritten loop.\n>\n> > diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> > index f1617260064..1454ab1ae6f 100644\n> > --- a/builtin/ls-files.c\n> > +++ b/builtin/ls-files.c\n> > @@ -312,51 +312,39 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n> >               if (show_killed)\n> >                       show_killed_files(repo->index, dir);\n> >       }\n> > -     if (show_cached || show_stage) {\n> > -             for (i = 0; i < repo->index->cache_nr; i++) {\n> > -                     const struct cache_entry *ce = repo->index->cache[i];\n> > +     if (! (show_cached || show_stage || show_deleted || show_modified))\n> > +             return;\n>\n> If none of these four are given, nothing will be given after this\n> point, so returning early is good.\n>\nI understand.\n> > +     for (i = 0; i < repo->index->cache_nr; i++) {\n> > +             const struct cache_entry *ce = repo->index->cache[i];\n> > +             struct stat st;\n> > +             int err;\n> >\n> > +             construct_fullname(&fullname, repo, ce);\n> >\n> > +             if ((dir->flags & DIR_SHOW_IGNORED) &&\n> > +                     !ce_excluded(dir, repo->index, fullname.buf, ce))\n> > +                     continue;\n> > +             if (ce->ce_flags & CE_UPDATE)\n> > +                     continue;\n>\n> The above two are common between the original two codepaths, and\n> merging them is good.\n>\n> > +             if (show_cached || show_stage) {\n> > +                     if (!show_unmerged || ce_stage(ce))\n> > +                             show_ce(repo, dir, ce, fullname.buf,\n> > +                                     ce_stage(ce) ? tag_unmerged :\n> > +                                     (ce_skip_worktree(ce) ? tag_skip_worktree :\n> > +                                             tag_cached));\n> >               }\n>\n> We would want to reduce the indentation level of the show_ce() by\n> consolidating the nested if/if to\n>\n>                 if ((show_cached || show_stage) &&\n>                     (!show_unmerged || ce_stage(ce)))\n>                         show_ce(...);\n>\n>\nThe reason for this may be I gave\n\"if(show_cached || show_stage)\" in 3/3\nAdded some logic.\n> Everything below from this point should be skipped (especially, the\n> call to lstat()) unless show_modified and/or show_deleted was asked\n> by the caller, i.e.  we want to insert\n>\n>                 if (!(show_deleted || show_modified))\n>                         continue;\n>\nI agree.\n> here, before we call ce_skip_worktree(), I think.\n>\n> > +             if (ce_skip_worktree(ce))\n> > +                     continue;\n> > +             err = lstat(fullname.buf, &st);\n> > +             if (err) {\n> > +                     if (errno != ENOENT && errno != ENOTDIR)\n> > +                             error_errno(\"cannot lstat '%s'\", fullname.buf);\n> > +                     if (show_deleted)\n> > +                             show_ce(repo, dir, ce, fullname.buf, tag_removed);\n> > +                     if (show_modified)\n> >                               show_ce(repo, dir, ce, fullname.buf, tag_modified);\n> > -             }\n> > +             } else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n> > +                     show_ce(repo, dir, ce, fullname.buf, tag_modified);\n> >       }\n>\n> And this part would look somewhat different if we take my earlier\n> suggestion for [1/3].\n>\nFine.\n> Thanks.\n"},{"id":"414921","messageId":"xmqq1reec943.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"CAOLTT8R=fF00WCVBSTDKHG_3p5RcZaxM2AU-cUj1sNWvy=mhCQ@mail.gmail.com","subject":"Re: [PATCH v5 3/3] ls-files.c: add --deduplicate option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-21T20:45:16Z","receivedAt":"2021-01-21T20:46:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"胡哲宁 <adlternative@gmail.com> writes:\n\n>> OK.  When show_stage is set, skipping_duplicates is automatically\n>> turned off (and show_unmerged is automatically covered as it turns\n>> show_stage on automatically).  So this feature has really become\n>> \"are we showing only names, and if so, did we show an entry of the\n>> same name before?\".\n> Yeah,showing only names,so I yesterday ask such question :)\n>>\n>> >                       if (!show_unmerged || ce_stage(ce))\n>> >                               show_ce(repo, dir, ce, fullname.buf,\n>> >                                       ce_stage(ce) ? tag_unmerged :\n>> >                                       (ce_skip_worktree(ce) ? tag_skip_worktree :\n>> >                                               tag_cached));\n>> > +                     if (show_cached && skipping_duplicates)\n>> > +                             last_shown_ce = ce;\n>>\n>> The code that calls show_ce() belonging to a totally separate if()\n>> statement makes my stomach hurt---how are we going to guarantee that\n>> \"last shown\" really will keep track of what was shown last?\n>>\n>> Shouldn't the above be more like this?\n>>\n>> -                       if (!show_unmerged || ce_stage(ce))\n>> +                       if (!show_unmerged || ce_stage(ce)) {\n>>                                 show_ce(repo, dir, ce, fullname.buf,\n>>                                         ce_stage(ce) ? tag_unmerged :\n>>                                         (ce_skip_worktree(ce) ? tag_skip_worktree :\n>>                                                 tag_cached));\n>> +                               last_shown_ce = ce;\n>> +                       }\n>>\n> well,I am also thinking about this question :\"last_shown_ce\" is not true\n> last shown ce,but may be If \"last_shown_ce\" truly seen every last shown\n> ce ,We may need more cumbersome logic to make the program correct.\n> I have tried the processing method of your above code before, but found\n>  that some errors may have occurred.\n\nI think judicious use of \"goto\" without introducing the last_shown\nwould probably result in a much more maintainable code.  It may look\nsomewhat like so:\n\n\tfor (i = 0; i < repo->index->cache_nr; i++) {\n\t\tconst struct cache_entry *ce = repo->index->cache[i];\n\t\tstruct stat st;\n\t\tint stat_err;\n\n\t\tconstruct_fullname(&fullname, repo, ce);\n\n\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n\t\t\tcontinue;\n\t\tif (ce->ce_flags & CE_UPDATE)\n\t\t\tcontinue;\n\t\tif ((show_cached || show_stage) &&\n\t\t    (!show_unmerged || ce_stage(ce))) {\n\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n\t\t\t\tce_stage(ce) ? tag_unmerged :\n\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n\t\t\t\t tag_cached));\n\t\t\tif (skip_duplicates)\n\t\t\t\tgoto skip_to_next_name;\n\t\t}\n\n\t\tif (!show_deleted && !show_modified)\n\t\t\tcontinue;\n\t\tif (ce_skip_worktree(ce))\n\t\t\tcontinue;\n\t\tstat_err = lstat(fullname.buf, &st);\n\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n\n\t\tif (show_deleted) {\n\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n\t\t\tif (skip_duplicates)\n\t\t\t\tgoto skip_to_next_name;\n\t\t}\n\t\tif (show_modified &&\n\t\t    (stat_err || ie_modified(repo->index, ce, &st, 0)))\n\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n\t\tcontinue;\n\n\tskip_to_next_name:\n\t\t{\n\t\t\tint j;\n\t\t\tconst struct cache_entry **cache = repo->index->cache;\n\t\t\tfor (j = i + 1; j < repo->index->cache_nr; j++)\n\t\t\t\tif (strcmp(ce->ce_name, cache[j]->ce_name))\n\t\t\t\t\tbreak;\n\t\t\ti = j - 1; /* compensate for outer for loop */\n\t\t}\n\t}\n"},{"id":"414954","messageId":"CAOLTT8Qp2NMpbk56U6PVEmFVyZYWN6gM83HD4z_nmPWV4Z_ruw@mail.gmail.com","threadId":"54943","inReplyTo":"xmqq1reec943.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 3/3] ls-files.c: add --deduplicate option","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-22T09:50:32Z","receivedAt":"2021-01-22T10:25:22Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年1月22日周五 上午4:45写道：\n>\n> 胡哲宁 <adlternative@gmail.com> writes:\n>\n> >> OK.  When show_stage is set, skipping_duplicates is automatically\n> >> turned off (and show_unmerged is automatically covered as it turns\n> >> show_stage on automatically).  So this feature has really become\n> >> \"are we showing only names, and if so, did we show an entry of the\n> >> same name before?\".\n> > Yeah,showing only names,so I yesterday ask such question :)\n> >>\n> >> >                       if (!show_unmerged || ce_stage(ce))\n> >> >                               show_ce(repo, dir, ce, fullname.buf,\n> >> >                                       ce_stage(ce) ? tag_unmerged :\n> >> >                                       (ce_skip_worktree(ce) ? tag_skip_worktree :\n> >> >                                               tag_cached));\n> >> > +                     if (show_cached && skipping_duplicates)\n> >> > +                             last_shown_ce = ce;\n> >>\n> >> The code that calls show_ce() belonging to a totally separate if()\n> >> statement makes my stomach hurt---how are we going to guarantee that\n> >> \"last shown\" really will keep track of what was shown last?\n> >>\n> >> Shouldn't the above be more like this?\n> >>\n> >> -                       if (!show_unmerged || ce_stage(ce))\n> >> +                       if (!show_unmerged || ce_stage(ce)) {\n> >>                                 show_ce(repo, dir, ce, fullname.buf,\n> >>                                         ce_stage(ce) ? tag_unmerged :\n> >>                                         (ce_skip_worktree(ce) ? tag_skip_worktree :\n> >>                                                 tag_cached));\n> >> +                               last_shown_ce = ce;\n> >> +                       }\n> >>\n> > well,I am also thinking about this question :\"last_shown_ce\" is not true\n> > last shown ce,but may be If \"last_shown_ce\" truly seen every last shown\n> > ce ,We may need more cumbersome logic to make the program correct.\n> > I have tried the processing method of your above code before, but found\n> >  that some errors may have occurred.\n>\n> I think judicious use of \"goto\" without introducing the last_shown\n> would probably result in a much more maintainable code.  It may look\n> somewhat like so:\n>\n>         for (i = 0; i < repo->index->cache_nr; i++) {\n>                 const struct cache_entry *ce = repo->index->cache[i];\n>                 struct stat st;\n>                 int stat_err;\n>\n>                 construct_fullname(&fullname, repo, ce);\n>\n>                 if ((dir->flags & DIR_SHOW_IGNORED) &&\n>                         !ce_excluded(dir, repo->index, fullname.buf, ce))\n>                         continue;\n>                 if (ce->ce_flags & CE_UPDATE)\n>                         continue;\n>                 if ((show_cached || show_stage) &&\n>                     (!show_unmerged || ce_stage(ce))) {\n>                         show_ce(repo, dir, ce, fullname.buf,\n>                                 ce_stage(ce) ? tag_unmerged :\n>                                 (ce_skip_worktree(ce) ? tag_skip_worktree :\n>                                  tag_cached));\n>                         if (skip_duplicates)\n>                                 goto skip_to_next_name;\n>                 }\n>\n>                 if (!show_deleted && !show_modified)\n>                         continue;\n>                 if (ce_skip_worktree(ce))\n>                         continue;\n>                 stat_err = lstat(fullname.buf, &st);\n>                 if (stat_err && (errno != ENOENT && errno != ENOTDIR))\n>                         error_errno(\"cannot lstat '%s'\", fullname.buf);\n>\n>                 if (show_deleted) {\n>                         show_ce(repo, dir, ce, fullname.buf, tag_removed);\n>                         if (skip_duplicates)\n>                                 goto skip_to_next_name;\n>                 }\n>                 if (show_modified &&\n>                     (stat_err || ie_modified(repo->index, ce, &st, 0)))\n>                         show_ce(repo, dir, ce, fullname.buf, tag_modified);\n>                 continue;\n>\n>         skip_to_next_name:\n>                 {\n>                         int j;\n>                         const struct cache_entry **cache = repo->index->cache;\n>                         for (j = i + 1; j < repo->index->cache_nr; j++)\n>                                 if (strcmp(ce->ce_name, cache[j]->ce_name))\n>                                         break;\n>                         i = j - 1; /* compensate for outer for loop */\n>                 }\n>         }\nI have to admit that this is indeed a good way to skip with \"goto\".\nThanks for your help.\nAnd should I still use gitgitgadget PR on my origin branch \"dedup\"or\nsend patch on branch \"zh/ls-files-deduplicate\"?\n"},{"id":"414971","messageId":"nycvar.QRO.7.76.6.2101221702420.52@tvgsbejvaqbjf.bet","threadId":"54943","inReplyTo":"CAOLTT8Qp2NMpbk56U6PVEmFVyZYWN6gM83HD4z_nmPWV4Z_ruw@mail.gmail.com","subject":"Re: [PATCH v5 3/3] ls-files.c: add --deduplicate option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-01-22T16:04:55Z","receivedAt":"2021-01-22T16:06:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi 胡哲宁,\n\nOn Fri, 22 Jan 2021, 胡哲宁 wrote:\n\n> And should I still use gitgitgadget PR on my origin branch \"dedup\"or\n> send patch on branch \"zh/ls-files-deduplicate\"?\n\nThe way GitGitGadget is designed asks for contributors to adjust their\npatch(es) via interactive rebase, implementing the suggestions and\naddressing the concerns while doing so, then force-pushing, optionally\namending the first PR comment (i.e. the description) with a list of\nthose changes, and then submitting a new iteration via `/submit`.\n\nCiao,\nJohannes\n"},{"id":"414977","messageId":"20210122154640.3791035-1-adlternative@gmail.com","threadId":"54943","inReplyTo":"CAOLTT8R=fF00WCVBSTDKHG_3p5RcZaxM2AU-cUj1sNWvy=mhCQ@mail.gmail.com","subject":"[PATCH v6] ls-files.c: add --deduplicate option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-22T15:46:40Z","receivedAt":"2021-01-22T17:50:17Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"In order to provide users a better experience\nwhen viewing information about files in the index\nand the working tree, the `--deduplicate` option will suppress\nsome duplicate name under some conditions.\n\nIn a merge conflict, one file name of \"git ls-files\" output may\nappear multiple times. For example,now there is an unmerged path\n`a.c`,`a.c` will appear three times in the output of\n\"git ls-files\".We can use \"git ls-files --deduplicate\" to output\n`a.c` only one time.(unless `--stage` or `--unmerged` is\nused to view all the detailed information in the index)\n\nIn addition, if you use both `--delete` and `--modify` at\nthe same time, The `--deduplicate` option\ncan also suppress file name output.\n\nAdditional instructions:\nIn order to display entries information,`deduplicate` suppresses\nthe output of duplicate file names, not the output of duplicate\nentries information, so under the option of `-t`, `--stage`, `--unmerge`,\n`--deduplicate` will have no effect.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-ls-files.txt |  5 +++\n builtin/ls-files.c             | 46 +++++++++++++++++-------\n t/t3012-ls-files-dedup.sh      | 66 ++++++++++++++++++++++++++++++++++\n 3 files changed, 105 insertions(+), 12 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\ndiff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\nindex 0a3b5265b3..a05f063d3d 100644\n--- a/Documentation/git-ls-files.txt\n+++ b/Documentation/git-ls-files.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t\t(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*\n \t\t(-[c|d|o|i|s|u|k|m])*\n \t\t[--eol]\n+\t\t[--deduplicate]\n \t\t[-x <pattern>|--exclude=<pattern>]\n \t\t[-X <file>|--exclude-from=<file>]\n \t\t[--exclude-per-directory=<file>]\n@@ -80,6 +81,10 @@ OPTIONS\n \t\\0 line termination on output and do not quote filenames.\n \tSee OUTPUT below for more information.\n \n+--deduplicate::\n+\tSuppress duplicate entries when there are unmerged paths in index\n+\tor `--deleted` and `--modified` are combined.\n+\n -x <pattern>::\n --exclude=<pattern>::\n \tSkip untracked files matching pattern.\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 1454ab1ae6..e67dc1ff45 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -35,6 +35,7 @@ static int line_terminator = '\\n';\n static int debug_mode;\n static int show_eol;\n static int recurse_submodules;\n+static int skipping_duplicates;\n \n static const char *prefix;\n static int max_prefix_len;\n@@ -317,7 +318,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \tfor (i = 0; i < repo->index->cache_nr; i++) {\n \t\tconst struct cache_entry *ce = repo->index->cache[i];\n \t\tstruct stat st;\n-\t\tint err;\n+\t\tint stat_err;\n \n \t\tconstruct_fullname(&fullname, repo, ce);\n \n@@ -326,25 +327,43 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tcontinue;\n \t\tif (ce->ce_flags & CE_UPDATE)\n \t\t\tcontinue;\n-\t\tif (show_cached || show_stage) {\n-\t\t\tif (!show_unmerged || ce_stage(ce))\n+\t\tif ((show_cached || show_stage) &&\n+\t\t\t(!show_unmerged || ce_stage(ce))) {\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n \t\t\t\t\tce_stage(ce) ? tag_unmerged :\n \t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n \t\t\t\t\t\ttag_cached));\n+\t\t\tif (show_cached && skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n \t\t}\n+\t\tif (!show_deleted && !show_modified)\n+\t\t\tcontinue;\n \t\tif (ce_skip_worktree(ce))\n \t\t\tcontinue;\n-\t\terr = lstat(fullname.buf, &st);\n-\t\tif (err) {\n-\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n-\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n-\t\t\tif (show_deleted)\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified)\n+\t\tstat_err = lstat(fullname.buf, &st);\n+\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n+\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n+\t\tif (stat_err && show_deleted) {\n+\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\tif (skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n+\t\t}\n+\t\tif (show_modified &&\n+\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0))) {\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n-\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n-\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\tif (skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n+\t\t}\n+\t\tcontinue;\n+skip_to_next_name:\n+\t\t{\n+\t\t\tint j;\n+\t\t\tstruct cache_entry **cache = repo->index->cache;\n+\t\t\tfor (j = i + 1; j < repo->index->cache_nr; j++)\n+\t\t\t\tif (strcmp(ce->name, cache[j]->name))\n+\t\t\t\t\tbreak;\n+\t\t\ti = j - 1; /* compensate for outer for loop */\n+\t\t}\n \t}\n \n \tstrbuf_release(&fullname);\n@@ -571,6 +590,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n+\t\tOPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n \t\tOPT_END()\n \t};\n \n@@ -610,6 +630,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t * you also show the stage information.\n \t\t */\n \t\tshow_stage = 1;\n+\tif (show_tag || show_stage)\n+\t\tskipping_duplicates = 0;\n \tif (dir.exclude_per_dir)\n \t\texc_given = 1;\n \ndiff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\nnew file mode 100755\nindex 0000000000..2682b1f43a\n--- /dev/null\n+++ b/t/t3012-ls-files-dedup.sh\n@@ -0,0 +1,66 @@\n+#!/bin/sh\n+\n+test_description='git ls-files --deduplicate test'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\t>a.txt &&\n+\t>b.txt &&\n+\t>delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m base &&\n+\techo a >a.txt &&\n+\techo b >b.txt &&\n+\techo delete >delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m tip &&\n+\tgit tag tip &&\n+\tgit reset --hard HEAD^ &&\n+\techo change >a.txt &&\n+\tgit commit -a -m side &&\n+\tgit tag side\n+'\n+\n+test_expect_success 'git ls-files --deduplicate to show unique unmerged path' '\n+\ttest_must_fail git merge tip &&\n+\tgit ls-files --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit merge --abort\n+'\n+\n+test_expect_success 'git ls-files -d -m --deduplicate with different display options' '\n+\tgit reset --hard side &&\n+\ttest_must_fail git merge tip &&\n+\trm delete.txt &&\n+\tgit ls-files -d -m --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -t --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tC a.txt\n+\tC a.txt\n+\tC a.txt\n+\tR delete.txt\n+\tC delete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -c --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit merge --abort\n+'\n+\n+test_done\n-- \n2.30.0\n\n"},{"id":"414979","messageId":"xmqq8s8kalz3.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"nycvar.QRO.7.76.6.2101221702420.52@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v5 3/3] ls-files.c: add --deduplicate option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-22T18:02:40Z","receivedAt":"2021-01-22T18:11:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> And should I still use gitgitgadget PR on my origin branch \"dedup\"or\n>> send patch on branch \"zh/ls-files-deduplicate\"?\n>\n> The way GitGitGadget is designed asks for contributors to adjust their\n> patch(es) via interactive rebase, implementing the suggestions and\n> addressing the concerns while doing so, then force-pushing, optionally\n> amending the first PR comment (i.e. the description) with a list of\n> those changes, and then submitting a new iteration via `/submit`.\n\nThanks for clearly explaining the rules.\n\nAs I suspect many people are afraid of forcing their pushes, it\nwould assure them to explain that it is OK to force when them\nrestart the series from scratch by replacing the commits.\n\nAnd it would very much help on the receiving end when the\ndescription gets updated.\n\nJust being curious, but when a series hits 'next', would the way in\nwhich the user interacts with GGG change?  With or without GGG, what\nis done on the local side is not all that different---you build new\ncommits on top without disturbing the commits that are in 'next'.\nThen what?  Push it again (this time there is no need to force) and\nsubmit the additional ones via `/submit`?\n\nThanks.\n"},{"id":"414998","messageId":"xmqqr1mc7kyt.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"20210122154640.3791035-1-adlternative@gmail.com","subject":"Re: [PATCH v6] ls-files.c: add --deduplicate option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-22T20:52:42Z","receivedAt":"2021-01-22T21:06:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> In order to provide users a better experience\n> when viewing information about files in the index\n> and the working tree, the `--deduplicate` option will suppress\n> some duplicate name under some conditions.\n\nNow is it just a single patch squashing everything together?\nThat does not look like it.\n\n> @@ -317,7 +318,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>  \tfor (i = 0; i < repo->index->cache_nr; i++) {\n>  \t\tconst struct cache_entry *ce = repo->index->cache[i];\n>  \t\tstruct stat st;\n> -\t\tint err;\n> +\t\tint stat_err;\n>  \n>  \t\tconstruct_fullname(&fullname, repo, ce);\n>  \n> @@ -326,25 +327,43 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>  \t\t\tcontinue;\n>  \t\tif (ce->ce_flags & CE_UPDATE)\n>  \t\t\tcontinue;\n> -\t\tif (show_cached || show_stage) {\n> -\t\t\tif (!show_unmerged || ce_stage(ce))\n> +\t\tif ((show_cached || show_stage) &&\n> +\t\t\t(!show_unmerged || ce_stage(ce))) {\n>  \t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n>  \t\t\t\t\tce_stage(ce) ? tag_unmerged :\n>  \t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n>  \t\t\t\t\t\ttag_cached));\n> +\t\t\tif (show_cached && skipping_duplicates)\n> +\t\t\t\tgoto skip_to_next_name;\n\nWhy should this be so complex?  You are dropping skipping_duplicates\nwhen the output is not name-only, so shouldn't this look more like\n\n\t\tif ((show_cached || show_stage) &&\n\t\t    (!show_unmerged || ce_stage(ce)) {\n\t\t\tshow_ce(...);\n                        if (skipping_duplicates)\n                        \tgoto skip_to_next_name;\n\t\t}\n\nIt seems that this still depends on the 2/3 from the previous\niteration, against which I suggested to merge the conditions of\nnested if statements into one.  That should be done in the updated\n2/3, not in this step, no?\n\n>  \t\t}\n> +\t\tif (!show_deleted && !show_modified)\n> +\t\t\tcontinue;\n\nAnd this one also belongs to the step 2/3 that consolidates the two\nloops into one.\n\nI think you'd need to start from the three patches in v5, \"rebase -i\"\nnot just [3/3] but at least [2/3], too.\n\nThanks.\n"},{"id":"415037","messageId":"CAOLTT8T1MRVcZxsF6cum8F7aXA=58CBjoD1+SJh3rhPx2zS++g@mail.gmail.com","threadId":"54943","inReplyTo":"nycvar.QRO.7.76.6.2101221702420.52@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v5 3/3] ls-files.c: add --deduplicate option","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-23T08:20:02Z","receivedAt":"2021-01-23T08:21:08Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Hi Johannes Schindelin,\nThanks for prompt me how to choose, maybe I was a little confused\nabout the git workflow before, \"zh/ls-files-deduplicate\" this kind of\n branch I don't need to operate,right?\n\nWell, then I will modify my code on the original branch.\n\nJohannes Schindelin <Johannes.Schindelin@gmx.de> 于2021年1月23日周六 上午12:04写道：\n>\n> Hi 胡哲宁,\n>\n> On Fri, 22 Jan 2021, 胡哲宁 wrote:\n>\n> > And should I still use gitgitgadget PR on my origin branch \"dedup\"or\n> > send patch on branch \"zh/ls-files-deduplicate\"?\n>\n> The way GitGitGadget is designed asks for contributors to adjust their\n> patch(es) via interactive rebase, implementing the suggestions and\n> addressing the concerns while doing so, then force-pushing, optionally\n> amending the first PR comment (i.e. the description) with a list of\n> those changes, and then submitting a new iteration via `/submit`.\n>\n> Ciao,\n> Johannes\n"},{"id":"415038","messageId":"CAOLTT8Sx0v_xF8KOtf76P5T=BF+CJ3B_J=H2RDDtzezuTUUa2Q@mail.gmail.com","threadId":"54943","inReplyTo":"xmqqr1mc7kyt.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v6] ls-files.c: add --deduplicate option","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-23T08:27:11Z","receivedAt":"2021-01-23T08:28:08Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年1月23日周六 上午4:52写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> > In order to provide users a better experience\n> > when viewing information about files in the index\n> > and the working tree, the `--deduplicate` option will suppress\n> > some duplicate name under some conditions.\n>\n> Now is it just a single patch squashing everything together?\n> That does not look like it.\n>\n> > @@ -317,7 +318,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n> >       for (i = 0; i < repo->index->cache_nr; i++) {\n> >               const struct cache_entry *ce = repo->index->cache[i];\n> >               struct stat st;\n> > -             int err;\n> > +             int stat_err;\n> >\n> >               construct_fullname(&fullname, repo, ce);\n> >\n> > @@ -326,25 +327,43 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n> >                       continue;\n> >               if (ce->ce_flags & CE_UPDATE)\n> >                       continue;\n> > -             if (show_cached || show_stage) {\n> > -                     if (!show_unmerged || ce_stage(ce))\n> > +             if ((show_cached || show_stage) &&\n> > +                     (!show_unmerged || ce_stage(ce))) {\n> >                               show_ce(repo, dir, ce, fullname.buf,\n> >                                       ce_stage(ce) ? tag_unmerged :\n> >                                       (ce_skip_worktree(ce) ? tag_skip_worktree :\n> >                                               tag_cached));\n> > +                     if (show_cached && skipping_duplicates)\n> > +                             goto skip_to_next_name;\n>\n> Why should this be so complex?  You are dropping skipping_duplicates\n> when the output is not name-only, so shouldn't this look more like\n>\nTruly,I may have considered too much,if I have\n\"show_stage\",\"skipping_duplicates\"\nmust be false.\n>                 if ((show_cached || show_stage) &&\n>                     (!show_unmerged || ce_stage(ce)) {\n>                         show_ce(...);\n>                         if (skipping_duplicates)\n>                                 goto skip_to_next_name;\n>                 }\n>\n> It seems that this still depends on the 2/3 from the previous\n> iteration, against which I suggested to merge the conditions of\n> nested if statements into one.  That should be done in the updated\n> 2/3, not in this step, no?\n>\n> >               }\n> > +             if (!show_deleted && !show_modified)\n> > +                     continue;\n>\n> And this one also belongs to the step 2/3 that consolidates the two\n> loops into one.\n>\n> I think you'd need to start from the three patches in v5, \"rebase -i\"\n> not just [3/3] but at least [2/3], too.\n>\n> Thanks.\n\nThanks,I am rewriting.\n"},{"id":"415041","messageId":"pull.832.v6.git.1611397210.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v5.git.1611037846.gitgitgadget@gmail.com","subject":"[PATCH v6 0/3] builtin/ls-files.c:add git ls-file --dedup option","fromName":"阿德烈 via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-23T10:20:07Z","receivedAt":"2021-01-23T10:21:17Z","isPatch":true,"sender":{"key":"name:阿德烈","avatar":null},"body":"I am reading the source code of git ls-files and learned that git ls-files\nmay have duplicate files name when there are unmerged path in a branch merge\nor when different options are used at the same time. Users may fell confuse\nwhen they see these duplicate file names.\n\nAs Junio C Hamano said ,it have odd behaviour.\n\nTherefore, we can provide an additional option to git ls-files to delete\nthose repeated information.\n\nThis fixes https://github.com/gitgitgadget/git/issues/198\n\nThanks!\n\nZheNing Hu (3):\n  ls_files.c: bugfix for --deleted and --modified\n  ls_files.c: consolidate two for loops into one\n  ls-files.c: add --deduplicate option\n\n Documentation/git-ls-files.txt |  5 ++\n builtin/ls-files.c             | 85 ++++++++++++++++++++--------------\n t/t3012-ls-files-dedup.sh      | 66 ++++++++++++++++++++++++++\n 3 files changed, 121 insertions(+), 35 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\n\nbase-commit: 6d3ef5b467eccd2769f1aa1c555d317d3c8dc707\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-832%2Fadlternative%2Fls-files-dedup-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-832/adlternative/ls-files-dedup-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/832\n\nRange-diff vs v5:\n\n 1:  ec9464f6094 ! 1:  fbc38ce9075 ls_files.c: bugfix for --deleted and --modified\n     @@ Commit message\n      \n       ## builtin/ls-files.c ##\n      @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     + \t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n     + \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n     + \t\t\tstruct stat st;\n     +-\t\t\tint err;\n     ++\t\t\tint stat_err;\n     + \n     + \t\t\tconstruct_fullname(&fullname, repo, ce);\n     + \n     +@@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     + \t\t\t\tcontinue;\n       \t\t\tif (ce_skip_worktree(ce))\n       \t\t\t\tcontinue;\n     - \t\t\terr = lstat(fullname.buf, &st);\n     +-\t\t\terr = lstat(fullname.buf, &st);\n      -\t\t\tif (show_deleted && err)\n     --\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     ++\t\t\tstat_err = lstat(fullname.buf, &st);\n     ++\t\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n     ++\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n     ++\t\t\tif (stat_err && show_deleted)\n     + \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n      -\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n     -+\t\t\tif (err) {\n     -+\t\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n     -+\t\t\t\t    error_errno(\"cannot lstat '%s'\", fullname.buf);\n     -+\t\t\t\tif (show_deleted)\n     -+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     -+\t\t\t\tif (show_modified)\n     +-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     ++\t\t\tif (show_modified &&\n     ++\t\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n      +\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     -+\t\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n     - \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n       \t\t}\n       \t}\n     + \n 2:  802ff802be8 ! 2:  3997d390883 ls_files.c: consolidate two for loops into one\n     @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_s\n      -\tif (show_cached || show_stage) {\n      -\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n      -\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n     -+\tif (! (show_cached || show_stage || show_deleted || show_modified))\n     -+\t\treturn;\n     -+\tfor (i = 0; i < repo->index->cache_nr; i++) {\n     -+\t\tconst struct cache_entry *ce = repo->index->cache[i];\n     -+\t\tstruct stat st;\n     -+\t\tint err;\n     - \n     +-\n      -\t\t\tconstruct_fullname(&fullname, repo, ce);\n     -+\t\tconstruct_fullname(&fullname, repo, ce);\n     - \n     +-\n      -\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n      -\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n      -\t\t\t\tcontinue;\n     @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_s\n      -\t\t\t\tce_stage(ce) ? tag_unmerged :\n      -\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n      -\t\t\t\t tag_cached));\n     -+\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n     -+\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n     -+\t\t\tcontinue;\n     -+\t\tif (ce->ce_flags & CE_UPDATE)\n     -+\t\t\tcontinue;\n     -+\t\tif (show_cached || show_stage) {\n     -+\t\t\tif (!show_unmerged || ce_stage(ce))\n     -+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n     -+\t\t\t\t\tce_stage(ce) ? tag_unmerged :\n     -+\t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n     -+\t\t\t\t\t\ttag_cached));\n     - \t\t}\n     +-\t\t}\n      -\t}\n      -\tif (show_deleted || show_modified) {\n      -\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n      -\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n      -\t\t\tstruct stat st;\n     --\t\t\tint err;\n     --\n     +-\t\t\tint stat_err;\n     ++\tif (! (show_cached || show_stage || show_deleted || show_modified))\n     ++\t\treturn;\n     ++\tfor (i = 0; i < repo->index->cache_nr; i++) {\n     ++\t\tconst struct cache_entry *ce = repo->index->cache[i];\n     ++\t\tstruct stat st;\n     ++\t\tint stat_err;\n     + \n      -\t\t\tconstruct_fullname(&fullname, repo, ce);\n     --\n     ++\t\tconstruct_fullname(&fullname, repo, ce);\n     + \n      -\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n      -\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n      -\t\t\t\tcontinue;\n     @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_s\n      -\t\t\t\tcontinue;\n      -\t\t\tif (ce_skip_worktree(ce))\n      -\t\t\t\tcontinue;\n     --\t\t\terr = lstat(fullname.buf, &st);\n     --\t\t\tif (err) {\n     --\t\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n     --\t\t\t\t    error_errno(\"cannot lstat '%s'\", fullname.buf);\n     --\t\t\t\tif (show_deleted)\n     --\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     --\t\t\t\tif (show_modified)\n     +-\t\t\tstat_err = lstat(fullname.buf, &st);\n     +-\t\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n     +-\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n     +-\t\t\tif (stat_err && show_deleted)\n     +-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     +-\t\t\tif (show_modified &&\n     +-\t\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n      -\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     --\t\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n     ++\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n     ++\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n     ++\t\t\tcontinue;\n     ++\t\tif (ce->ce_flags & CE_UPDATE)\n     ++\t\t\tcontinue;\n     ++\t\tif (show_cached || show_stage) {\n     ++\t\t\tif (!show_unmerged || ce_stage(ce))\n     ++\t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n     ++\t\t\t\t\tce_stage(ce) ? tag_unmerged :\n     ++\t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n     ++\t\t\t\t\t\ttag_cached));\n     + \t\t}\n     ++\t\tif (!show_deleted && !show_modified)\n     ++\t\t\tcontinue;\n      +\t\tif (ce_skip_worktree(ce))\n      +\t\t\tcontinue;\n     -+\t\terr = lstat(fullname.buf, &st);\n     -+\t\tif (err) {\n     -+\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n     -+\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n     -+\t\t\tif (show_deleted)\n     -+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     -+\t\t\tif (show_modified)\n     - \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     --\t\t}\n     -+\t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n     -+\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     ++\t\tstat_err = lstat(fullname.buf, &st);\n     ++\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n     ++\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n     ++\t\tif (stat_err && show_deleted)\n     ++\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     ++\t\tif (show_modified &&\n     ++\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n     ++\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n       \t}\n       \n       \tstrbuf_release(&fullname);\n 3:  e9c53186706 ! 3:  07b603fd97c ls-files.c: add --deduplicate option\n     @@ builtin/ls-files.c: static int line_terminator = '\\n';\n       static const char *prefix;\n       static int max_prefix_len;\n      @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     - {\n     - \tint i;\n     - \tstruct strbuf fullname = STRBUF_INIT;\n     -+\tconst struct cache_entry *last_shown_ce;\n     - \n     - \t/* For cached/deleted files we don't need to even do the readdir */\n     - \tif (show_others || show_killed) {\n     -@@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     - \t}\n     - \tif (! (show_cached || show_stage || show_deleted || show_modified))\n     - \t\treturn;\n     -+\tlast_shown_ce = NULL;\n     - \tfor (i = 0; i < repo->index->cache_nr; i++) {\n     - \t\tconst struct cache_entry *ce = repo->index->cache[i];\n     - \t\tstruct stat st;\n     -@@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     - \n     - \t\tconstruct_fullname(&fullname, repo, ce);\n     - \n     -+\t\tif (skipping_duplicates && last_shown_ce &&\n     -+\t\t\t!strcmp(last_shown_ce->name,ce->name))\n     -+\t\t\t\tcontinue;\n     - \t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n     - \t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n       \t\t\tcontinue;\n       \t\tif (ce->ce_flags & CE_UPDATE)\n       \t\t\tcontinue;\n     - \t\tif (show_cached || show_stage) {\n     -+\t\t\tif (skipping_duplicates && last_shown_ce &&\n     -+\t\t\t\t!strcmp(last_shown_ce->name,ce->name))\n     -+\t\t\t\t\tcontinue;\n     - \t\t\tif (!show_unmerged || ce_stage(ce))\n     +-\t\tif (show_cached || show_stage) {\n     +-\t\t\tif (!show_unmerged || ce_stage(ce))\n     ++\t\tif ((show_cached || show_stage) &&\n     ++\t\t\t(!show_unmerged || ce_stage(ce))) {\n       \t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n       \t\t\t\t\tce_stage(ce) ? tag_unmerged :\n       \t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n       \t\t\t\t\t\ttag_cached));\n     -+\t\t\tif (show_cached && skipping_duplicates)\n     -+\t\t\t\tlast_shown_ce = ce;\n     ++\t\t\tif (skipping_duplicates)\n     ++\t\t\t\tgoto skip_to_next_name;\n       \t\t}\n     - \t\tif (ce_skip_worktree(ce))\n     + \t\tif (!show_deleted && !show_modified)\n       \t\t\tcontinue;\n     -+\t\tif (skipping_duplicates && last_shown_ce &&\n     -+\t\t\t!strcmp(last_shown_ce->name,ce->name))\n     -+\t\t\t\tcontinue;\n     - \t\terr = lstat(fullname.buf, &st);\n     - \t\tif (err) {\n     --\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n     --\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n     --\t\t\tif (show_deleted)\n     -+\t\t\tif (skipping_duplicates && show_deleted && show_modified)\n     - \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     --\t\t\tif (show_modified)\n     --\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     -+\t\t\telse {\n     -+\t\t\t\tif (errno != ENOENT && errno != ENOTDIR)\n     -+\t\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n     -+\t\t\t\tif (show_deleted)\n     -+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     -+\t\t\t\tif (show_modified)\n     -+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     -+\t\t\t}\n     - \t\t} else if (show_modified && ie_modified(repo->index, ce, &st, 0))\n     - \t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     -+\t\tlast_shown_ce = ce;\n     +@@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n     + \t\tstat_err = lstat(fullname.buf, &st);\n     + \t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n     + \t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n     +-\t\tif (stat_err && show_deleted)\n     ++\t\tif (stat_err && show_deleted) {\n     + \t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n     ++\t\t\tif (skipping_duplicates)\n     ++\t\t\t\tgoto skip_to_next_name;\n     ++\t\t}\n     + \t\tif (show_modified &&\n     +-\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n     ++\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0))) {\n     + \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n     ++\t\t\tif (skipping_duplicates)\n     ++\t\t\t\tgoto skip_to_next_name;\n     ++\t\t}\n     ++\t\tcontinue;\n     ++skip_to_next_name:\n     ++\t\t{\n     ++\t\t\tint j;\n     ++\t\t\tstruct cache_entry **cache = repo->index->cache;\n     ++\t\t\tfor (j = i + 1; j < repo->index->cache_nr; j++)\n     ++\t\t\t\tif (strcmp(ce->name, cache[j]->name))\n     ++\t\t\t\t\tbreak;\n     ++\t\t\ti = j - 1; /* compensate for outer for loop */\n     ++\t\t}\n       \t}\n       \n       \tstrbuf_release(&fullname);\n\n-- \ngitgitgadget\n"},{"id":"415042","messageId":"fbc38ce9075d3d28187351b0fb6b34a27ec431fd.1611397210.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v6.git.1611397210.gitgitgadget@gmail.com","subject":"[PATCH v6 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-23T10:20:08Z","receivedAt":"2021-01-23T10:21:17Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThis situation may occur in the original code: lstat() failed\nbut we use `&st` to feed ie_modified() later.\n\nTherefore, we can directly execute show_ce without the judgment of\nie_modified() when lstat() has failed.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n builtin/ls-files.c | 13 ++++++++-----\n 1 file changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex c8eae899b82..1e264bd1329 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -335,7 +335,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n \t\t\tstruct stat st;\n-\t\t\tint err;\n+\t\t\tint stat_err;\n \n \t\t\tconstruct_fullname(&fullname, repo, ce);\n \n@@ -346,11 +346,14 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\t\tcontinue;\n \t\t\tif (ce_skip_worktree(ce))\n \t\t\t\tcontinue;\n-\t\t\terr = lstat(fullname.buf, &st);\n-\t\t\tif (show_deleted && err)\n+\t\t\tstat_err = lstat(fullname.buf, &st);\n+\t\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n+\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n+\t\t\tif (stat_err && show_deleted)\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\tif (show_modified &&\n+\t\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n \t\t}\n \t}\n \n-- \ngitgitgadget\n\n"},{"id":"415043","messageId":"3997d390883e0ee13d343ad56cb535cd99d1af4e.1611397210.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v6.git.1611397210.gitgitgadget@gmail.com","subject":"[PATCH v6 2/3] ls_files.c: consolidate two for loops into one","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-23T10:20:09Z","receivedAt":"2021-01-23T10:21:17Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nRefactor the two for loops into one,skip showing the ce if it\nhas the same name as the previously shown one, only when doing so\nwon't lose information.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n builtin/ls-files.c | 70 ++++++++++++++++++++--------------------------\n 1 file changed, 30 insertions(+), 40 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 1e264bd1329..966c0ab0296 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -312,49 +312,39 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tif (show_killed)\n \t\t\tshow_killed_files(repo->index, dir);\n \t}\n-\tif (show_cached || show_stage) {\n-\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n-\n-\t\t\tconstruct_fullname(&fullname, repo, ce);\n-\n-\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (show_unmerged && !ce_stage(ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (ce->ce_flags & CE_UPDATE)\n-\t\t\t\tcontinue;\n-\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n-\t\t\t\tce_stage(ce) ? tag_unmerged :\n-\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n-\t\t\t\t tag_cached));\n-\t\t}\n-\t}\n-\tif (show_deleted || show_modified) {\n-\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n-\t\t\tstruct stat st;\n-\t\t\tint stat_err;\n+\tif (! (show_cached || show_stage || show_deleted || show_modified))\n+\t\treturn;\n+\tfor (i = 0; i < repo->index->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = repo->index->cache[i];\n+\t\tstruct stat st;\n+\t\tint stat_err;\n \n-\t\t\tconstruct_fullname(&fullname, repo, ce);\n+\t\tconstruct_fullname(&fullname, repo, ce);\n \n-\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (ce->ce_flags & CE_UPDATE)\n-\t\t\t\tcontinue;\n-\t\t\tif (ce_skip_worktree(ce))\n-\t\t\t\tcontinue;\n-\t\t\tstat_err = lstat(fullname.buf, &st);\n-\t\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n-\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n-\t\t\tif (stat_err && show_deleted)\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified &&\n-\t\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n-\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n+\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n+\t\t\tcontinue;\n+\t\tif (ce->ce_flags & CE_UPDATE)\n+\t\t\tcontinue;\n+\t\tif (show_cached || show_stage) {\n+\t\t\tif (!show_unmerged || ce_stage(ce))\n+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n+\t\t\t\t\tce_stage(ce) ? tag_unmerged :\n+\t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n+\t\t\t\t\t\ttag_cached));\n \t\t}\n+\t\tif (!show_deleted && !show_modified)\n+\t\t\tcontinue;\n+\t\tif (ce_skip_worktree(ce))\n+\t\t\tcontinue;\n+\t\tstat_err = lstat(fullname.buf, &st);\n+\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n+\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n+\t\tif (stat_err && show_deleted)\n+\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\tif (show_modified &&\n+\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n \t}\n \n \tstrbuf_release(&fullname);\n-- \ngitgitgadget\n\n"},{"id":"415044","messageId":"07b603fd97c0e2d7a70407da552c3c0658a84731.1611397210.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v6.git.1611397210.gitgitgadget@gmail.com","subject":"[PATCH v6 3/3] ls-files.c: add --deduplicate option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-23T10:20:10Z","receivedAt":"2021-01-23T10:21:17Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nIn order to provide users a better experience\nwhen viewing information about files in the index\nand the working tree, the `--deduplicate` option will suppress\nsome duplicate name under some conditions.\n\nIn a merge conflict, one file name of \"git ls-files\" output may\nappear multiple times. For example,now there is an unmerged path\n`a.c`,`a.c` will appear three times in the output of\n\"git ls-files\".We can use \"git ls-files --deduplicate\" to output\n`a.c` only one time.(unless `--stage` or `--unmerged` is\nused to view all the detailed information in the index)\n\nIn addition, if you use both `--delete` and `--modify` at\nthe same time, The `--deduplicate` option\ncan also suppress file name output.\n\nAdditional instructions:\nIn order to display entries information,`deduplicate` suppresses\nthe output of duplicate file names, not the output of duplicate\nentries information, so under the option of `-t`, `--stage`, `--unmerge`,\n`--deduplicate` will have no effect.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n Documentation/git-ls-files.txt |  5 +++\n builtin/ls-files.c             | 30 +++++++++++++---\n t/t3012-ls-files-dedup.sh      | 66 ++++++++++++++++++++++++++++++++++\n 3 files changed, 97 insertions(+), 4 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\ndiff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\nindex cbcf5263dd0..d11c8ade402 100644\n--- a/Documentation/git-ls-files.txt\n+++ b/Documentation/git-ls-files.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t\t(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*\n \t\t(-[c|d|o|i|s|u|k|m])*\n \t\t[--eol]\n+\t\t[--deduplicate]\n \t\t[-x <pattern>|--exclude=<pattern>]\n \t\t[-X <file>|--exclude-from=<file>]\n \t\t[--exclude-per-directory=<file>]\n@@ -81,6 +82,10 @@ OPTIONS\n \t\\0 line termination on output and do not quote filenames.\n \tSee OUTPUT below for more information.\n \n+--deduplicate::\n+\tSuppress duplicate entries when there are unmerged paths in index\n+\tor `--deleted` and `--modified` are combined.\n+\n -x <pattern>::\n --exclude=<pattern>::\n \tSkip untracked files matching pattern.\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 966c0ab0296..fb9cf50d764 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -35,6 +35,7 @@ static int line_terminator = '\\n';\n static int debug_mode;\n static int show_eol;\n static int recurse_submodules;\n+static int skipping_duplicates;\n \n static const char *prefix;\n static int max_prefix_len;\n@@ -326,12 +327,14 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tcontinue;\n \t\tif (ce->ce_flags & CE_UPDATE)\n \t\t\tcontinue;\n-\t\tif (show_cached || show_stage) {\n-\t\t\tif (!show_unmerged || ce_stage(ce))\n+\t\tif ((show_cached || show_stage) &&\n+\t\t\t(!show_unmerged || ce_stage(ce))) {\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n \t\t\t\t\tce_stage(ce) ? tag_unmerged :\n \t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n \t\t\t\t\t\ttag_cached));\n+\t\t\tif (skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n \t\t}\n \t\tif (!show_deleted && !show_modified)\n \t\t\tcontinue;\n@@ -340,11 +343,27 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tstat_err = lstat(fullname.buf, &st);\n \t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n \t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n-\t\tif (stat_err && show_deleted)\n+\t\tif (stat_err && show_deleted) {\n \t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\tif (skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n+\t\t}\n \t\tif (show_modified &&\n-\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n+\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0))) {\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\tif (skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n+\t\t}\n+\t\tcontinue;\n+skip_to_next_name:\n+\t\t{\n+\t\t\tint j;\n+\t\t\tstruct cache_entry **cache = repo->index->cache;\n+\t\t\tfor (j = i + 1; j < repo->index->cache_nr; j++)\n+\t\t\t\tif (strcmp(ce->name, cache[j]->name))\n+\t\t\t\t\tbreak;\n+\t\t\ti = j - 1; /* compensate for outer for loop */\n+\t\t}\n \t}\n \n \tstrbuf_release(&fullname);\n@@ -571,6 +590,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n+\t\tOPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n \t\tOPT_END()\n \t};\n \n@@ -610,6 +630,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t * you also show the stage information.\n \t\t */\n \t\tshow_stage = 1;\n+\tif (show_tag || show_stage)\n+\t\tskipping_duplicates = 0;\n \tif (dir.exclude_per_dir)\n \t\texc_given = 1;\n \ndiff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\nnew file mode 100755\nindex 00000000000..2682b1f43a6\n--- /dev/null\n+++ b/t/t3012-ls-files-dedup.sh\n@@ -0,0 +1,66 @@\n+#!/bin/sh\n+\n+test_description='git ls-files --deduplicate test'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\t>a.txt &&\n+\t>b.txt &&\n+\t>delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m base &&\n+\techo a >a.txt &&\n+\techo b >b.txt &&\n+\techo delete >delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m tip &&\n+\tgit tag tip &&\n+\tgit reset --hard HEAD^ &&\n+\techo change >a.txt &&\n+\tgit commit -a -m side &&\n+\tgit tag side\n+'\n+\n+test_expect_success 'git ls-files --deduplicate to show unique unmerged path' '\n+\ttest_must_fail git merge tip &&\n+\tgit ls-files --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit merge --abort\n+'\n+\n+test_expect_success 'git ls-files -d -m --deduplicate with different display options' '\n+\tgit reset --hard side &&\n+\ttest_must_fail git merge tip &&\n+\trm delete.txt &&\n+\tgit ls-files -d -m --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -t --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tC a.txt\n+\tC a.txt\n+\tC a.txt\n+\tR delete.txt\n+\tC delete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -c --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit merge --abort\n+'\n+\n+test_done\n-- \ngitgitgadget\n"},{"id":"415071","messageId":"xmqqwnw34jxx.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"fbc38ce9075d3d28187351b0fb6b34a27ec431fd.1611397210.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-23T17:55:22Z","receivedAt":"2021-01-23T17:56:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> This situation may occur in the original code: lstat() failed\n> but we use `&st` to feed ie_modified() later.\n>\n> Therefore, we can directly execute show_ce without the judgment of\n> ie_modified() when lstat() has failed.\n>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  builtin/ls-files.c | 13 ++++++++-----\n>  1 file changed, 8 insertions(+), 5 deletions(-)\n\nLooks good.  I think we are finished with this part, except for one\nnit.\n\n> +\t\t\tif (stat_err && show_deleted)\n>  \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n> -\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n> -\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n> +\t\t\tif (show_modified &&\n> +\t\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n> +\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n>  \t\t}\n\nThe last line is misindented by having one leading horizontal tab\ntoo many.  show_ce() for modified files and show_ce() for deleted\nfiles are done independently under different conditions and stand as\nequals, so the beginning of them should align to show that.\n\nPerhaps format the last three lines more like so:\n\n\t\t\tif (show_modified &&\n\t\t\t    (stat_err || ie_modified(repo->index, ce, &st, 0)))\n\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n\nAgain this would cascade throughout the sreies, but let's see if\nthere are other things we may want to change in the rest of the\nseries first.  Otherwise, instead of having you rebase, I probably\nhave time to tweak the series on my end while queuing.\n\nThanks.\n"},{"id":"415077","messageId":"xmqq8s8j4eld.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"3997d390883e0ee13d343ad56cb535cd99d1af4e.1611397210.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 2/3] ls_files.c: consolidate two for loops into one","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-23T19:50:54Z","receivedAt":"2021-01-23T19:51:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> Refactor the two for loops into one,skip showing the ce if it\n> has the same name as the previously shown one, only when doing so\n> won't lose information.\n\nThis message is all stale now.  This step does only refactoring,\nwithout \"skip showing\" and others.\n\nI've rebased the series locally and sending out a \"v7\" for your\nreview later.\n\nThanks.\n\n>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> ---\n>  builtin/ls-files.c | 70 ++++++++++++++++++++--------------------------\n>  1 file changed, 30 insertions(+), 40 deletions(-)\n>\n> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> index 1e264bd1329..966c0ab0296 100644\n> --- a/builtin/ls-files.c\n> +++ b/builtin/ls-files.c\n> @@ -312,49 +312,39 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>  \t\tif (show_killed)\n>  \t\t\tshow_killed_files(repo->index, dir);\n>  \t}\n> -\tif (show_cached || show_stage) {\n> -\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n> -\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n> -\n> -\t\t\tconstruct_fullname(&fullname, repo, ce);\n> -\n> -\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n> -\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n> -\t\t\t\tcontinue;\n> -\t\t\tif (show_unmerged && !ce_stage(ce))\n> -\t\t\t\tcontinue;\n> -\t\t\tif (ce->ce_flags & CE_UPDATE)\n> -\t\t\t\tcontinue;\n> -\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n> -\t\t\t\tce_stage(ce) ? tag_unmerged :\n> -\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n> -\t\t\t\t tag_cached));\n> -\t\t}\n> -\t}\n> -\tif (show_deleted || show_modified) {\n> -\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n> -\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n> -\t\t\tstruct stat st;\n> -\t\t\tint stat_err;\n> +\tif (! (show_cached || show_stage || show_deleted || show_modified))\n> +\t\treturn;\n> +\tfor (i = 0; i < repo->index->cache_nr; i++) {\n> +\t\tconst struct cache_entry *ce = repo->index->cache[i];\n> +\t\tstruct stat st;\n> +\t\tint stat_err;\n>  \n> -\t\t\tconstruct_fullname(&fullname, repo, ce);\n> +\t\tconstruct_fullname(&fullname, repo, ce);\n>  \n> -\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n> -\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n> -\t\t\t\tcontinue;\n> -\t\t\tif (ce->ce_flags & CE_UPDATE)\n> -\t\t\t\tcontinue;\n> -\t\t\tif (ce_skip_worktree(ce))\n> -\t\t\t\tcontinue;\n> -\t\t\tstat_err = lstat(fullname.buf, &st);\n> -\t\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n> -\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n> -\t\t\tif (stat_err && show_deleted)\n> -\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n> -\t\t\tif (show_modified &&\n> -\t\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n> -\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n> +\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n> +\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n> +\t\t\tcontinue;\n> +\t\tif (ce->ce_flags & CE_UPDATE)\n> +\t\t\tcontinue;\n> +\t\tif (show_cached || show_stage) {\n> +\t\t\tif (!show_unmerged || ce_stage(ce))\n> +\t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n> +\t\t\t\t\tce_stage(ce) ? tag_unmerged :\n> +\t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n> +\t\t\t\t\t\ttag_cached));\n>  \t\t}\n> +\t\tif (!show_deleted && !show_modified)\n> +\t\t\tcontinue;\n> +\t\tif (ce_skip_worktree(ce))\n> +\t\t\tcontinue;\n> +\t\tstat_err = lstat(fullname.buf, &st);\n> +\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n> +\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n> +\t\tif (stat_err && show_deleted)\n> +\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n> +\t\tif (show_modified &&\n> +\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n> +\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n>  \t}\n>  \n>  \tstrbuf_release(&fullname);\n"},{"id":"415078","messageId":"xmqq4kj74ek9.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"07b603fd97c0e2d7a70407da552c3c0658a84731.1611397210.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 3/3] ls-files.c: add --deduplicate option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-23T19:51:34Z","receivedAt":"2021-01-23T19:52:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Additional instructions:\n> In order to display entries information,`deduplicate` suppresses\n> the output of duplicate file names, not the output of duplicate\n> entries information, so under the option of `-t`, `--stage`, `--unmerge`,\n> `--deduplicate` will have no effect.\n\nThat information belongs to the end-user documentation.\n"},{"id":"415079","messageId":"20210123195334.2970374-1-gitster@pobox.com","threadId":"54943","inReplyTo":"pull.832.v6.git.1611397210.gitgitgadget@gmail.com","subject":"[PATCH v7 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-23T19:53:32Z","receivedAt":"2021-01-23T19:54:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThis situation may occur in the original code: lstat() failed\nbut we use `&st` to feed ie_modified() later.\n\nTherefore, we can directly execute show_ce without the judgment of\nie_modified() when lstat() has failed.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n[jc: fixed misindented code]\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/ls-files.c | 11 +++++++----\n 1 file changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex c8eae899b8..ce6f6ad00e 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -335,7 +335,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n \t\t\tstruct stat st;\n-\t\t\tint err;\n+\t\t\tint stat_err;\n \n \t\t\tconstruct_fullname(&fullname, repo, ce);\n \n@@ -346,10 +346,13 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\t\tcontinue;\n \t\t\tif (ce_skip_worktree(ce))\n \t\t\t\tcontinue;\n-\t\t\terr = lstat(fullname.buf, &st);\n-\t\t\tif (show_deleted && err)\n+\t\t\tstat_err = lstat(fullname.buf, &st);\n+\t\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n+\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n+\t\t\tif (stat_err && show_deleted)\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n+\t\t\tif (show_modified &&\n+\t\t\t    (stat_err || ie_modified(repo->index, ce, &st, 0)))\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n \t\t}\n \t}\n-- \n2.30.0-491-g302c625a7b\n\n"},{"id":"415080","messageId":"20210123195334.2970374-2-gitster@pobox.com","threadId":"54943","inReplyTo":"20210123195334.2970374-1-gitster@pobox.com","subject":"[PATCH v7 2/3] ls_files.c: consolidate two for loops into one","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-23T19:53:33Z","receivedAt":"2021-01-23T19:54:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThis will make it easier to show only one entry per filename in the\nnext step.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n[jc: corrected the log message]\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/ls-files.c | 63 ++++++++++++++++++++--------------------------\n 1 file changed, 27 insertions(+), 36 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex ce6f6ad00e..e94d724aff 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -312,49 +312,40 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tif (show_killed)\n \t\t\tshow_killed_files(repo->index, dir);\n \t}\n-\tif (show_cached || show_stage) {\n-\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n \n-\t\t\tconstruct_fullname(&fullname, repo, ce);\n+\tif (!(show_cached || show_stage || show_deleted || show_modified))\n+\t\treturn;\n+\tfor (i = 0; i < repo->index->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = repo->index->cache[i];\n+\t\tstruct stat st;\n+\t\tint stat_err;\n \n-\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (show_unmerged && !ce_stage(ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (ce->ce_flags & CE_UPDATE)\n-\t\t\t\tcontinue;\n+\t\tconstruct_fullname(&fullname, repo, ce);\n+\n+\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n+\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n+\t\t\tcontinue;\n+\t\tif (ce->ce_flags & CE_UPDATE)\n+\t\t\tcontinue;\n+\t\tif ((show_cached || show_stage) &&\n+\t\t    (!show_unmerged || ce_stage(ce)))\n \t\t\tshow_ce(repo, dir, ce, fullname.buf,\n \t\t\t\tce_stage(ce) ? tag_unmerged :\n \t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n \t\t\t\t tag_cached));\n-\t\t}\n-\t}\n-\tif (show_deleted || show_modified) {\n-\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n-\t\t\tstruct stat st;\n-\t\t\tint stat_err;\n-\n-\t\t\tconstruct_fullname(&fullname, repo, ce);\n \n-\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (ce->ce_flags & CE_UPDATE)\n-\t\t\t\tcontinue;\n-\t\t\tif (ce_skip_worktree(ce))\n-\t\t\t\tcontinue;\n-\t\t\tstat_err = lstat(fullname.buf, &st);\n-\t\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n-\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n-\t\t\tif (stat_err && show_deleted)\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified &&\n-\t\t\t    (stat_err || ie_modified(repo->index, ce, &st, 0)))\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n-\t\t}\n+\t\tif (!(show_deleted || show_modified))\n+\t\t\tcontinue;\n+\t\tif (ce_skip_worktree(ce))\n+\t\t\tcontinue;\n+\t\tstat_err = lstat(fullname.buf, &st);\n+\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n+\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n+\t\tif (stat_err && show_deleted)\n+\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\tif (show_modified &&\n+\t\t    (stat_err || ie_modified(repo->index, ce, &st, 0)))\n+\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n \t}\n \n \tstrbuf_release(&fullname);\n-- \n2.30.0-491-g302c625a7b\n\n"},{"id":"415081","messageId":"20210123195334.2970374-3-gitster@pobox.com","threadId":"54943","inReplyTo":"20210123195334.2970374-1-gitster@pobox.com","subject":"[PATCH v7 3/3] ls-files.c: add --deduplicate option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-23T19:53:34Z","receivedAt":"2021-01-23T19:54:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nDuring a merge conflict, the name of a file may appear multiple\ntimes in \"git ls-files\" output, once for each stage.  If you use\nboth `--delete` and `--modify` at the same time, the output may\nmention a deleted file twice.\n\nWhen none of the '-t', '-u', or '-s' options is in use, these\nduplicate entries do not add much value to the output.\n\nIntroduce a new '--deduplicate' option to suppress them.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n[jc: extended doc and rewritten commit log]\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-ls-files.txt |  8 +++++\n builtin/ls-files.c             | 31 ++++++++++++++--\n t/t3012-ls-files-dedup.sh      | 66 ++++++++++++++++++++++++++++++++++\n 3 files changed, 102 insertions(+), 3 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\ndiff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\nindex 0a3b5265b3..6d11ab506b 100644\n--- a/Documentation/git-ls-files.txt\n+++ b/Documentation/git-ls-files.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t\t(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*\n \t\t(-[c|d|o|i|s|u|k|m])*\n \t\t[--eol]\n+\t\t[--deduplicate]\n \t\t[-x <pattern>|--exclude=<pattern>]\n \t\t[-X <file>|--exclude-from=<file>]\n \t\t[--exclude-per-directory=<file>]\n@@ -80,6 +81,13 @@ OPTIONS\n \t\\0 line termination on output and do not quote filenames.\n \tSee OUTPUT below for more information.\n \n+--deduplicate::\n+\tWhen only filenames are shown, suppress duplicates that may\n+\tcome from having multiple stages during a merge, or giving\n+\t`--deleted` and `--modified` option at the same time.\n+\tWhen any of the `-t`, `--unmerged`, or `--stage` option is\n+\tin use, this option has no effect.\n+\n -x <pattern>::\n --exclude=<pattern>::\n \tSkip untracked files matching pattern.\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex e94d724aff..f6f9e483b2 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -35,6 +35,7 @@ static int line_terminator = '\\n';\n static int debug_mode;\n static int show_eol;\n static int recurse_submodules;\n+static int skipping_duplicates;\n \n static const char *prefix;\n static int max_prefix_len;\n@@ -328,11 +329,14 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tif (ce->ce_flags & CE_UPDATE)\n \t\t\tcontinue;\n \t\tif ((show_cached || show_stage) &&\n-\t\t    (!show_unmerged || ce_stage(ce)))\n+\t\t    (!show_unmerged || ce_stage(ce))) {\n \t\t\tshow_ce(repo, dir, ce, fullname.buf,\n \t\t\t\tce_stage(ce) ? tag_unmerged :\n \t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n \t\t\t\t tag_cached));\n+\t\t\tif (skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n+\t\t}\n \n \t\tif (!(show_deleted || show_modified))\n \t\t\tcontinue;\n@@ -341,11 +345,28 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tstat_err = lstat(fullname.buf, &st);\n \t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n \t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n-\t\tif (stat_err && show_deleted)\n+\t\tif (stat_err && show_deleted) {\n \t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\tif (skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n+\t\t}\n \t\tif (show_modified &&\n-\t\t    (stat_err || ie_modified(repo->index, ce, &st, 0)))\n+\t\t    (stat_err || ie_modified(repo->index, ce, &st, 0))) {\n \t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\tif (skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n+\t\t}\n+\t\tcontinue;\n+\n+skip_to_next_name:\n+\t\t{\n+\t\t\tint j;\n+\t\t\tstruct cache_entry **cache = repo->index->cache;\n+\t\t\tfor (j = i + 1; j < repo->index->cache_nr; j++)\n+\t\t\t\tif (strcmp(ce->name, cache[j]->name))\n+\t\t\t\t\tbreak;\n+\t\t\ti = j - 1; /* compensate for the for loop */\n+\t\t}\n \t}\n \n \tstrbuf_release(&fullname);\n@@ -572,6 +593,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n+\t\tOPT_BOOL(0, \"deduplicate\", &skipping_duplicates,\n+\t\t\t N_(\"suppress duplicate entries\")),\n \t\tOPT_END()\n \t};\n \n@@ -611,6 +634,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t * you also show the stage information.\n \t\t */\n \t\tshow_stage = 1;\n+\tif (show_tag || show_stage)\n+\t\tskipping_duplicates = 0;\n \tif (dir.exclude_per_dir)\n \t\texc_given = 1;\n \ndiff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\nnew file mode 100755\nindex 0000000000..2682b1f43a\n--- /dev/null\n+++ b/t/t3012-ls-files-dedup.sh\n@@ -0,0 +1,66 @@\n+#!/bin/sh\n+\n+test_description='git ls-files --deduplicate test'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\t>a.txt &&\n+\t>b.txt &&\n+\t>delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m base &&\n+\techo a >a.txt &&\n+\techo b >b.txt &&\n+\techo delete >delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m tip &&\n+\tgit tag tip &&\n+\tgit reset --hard HEAD^ &&\n+\techo change >a.txt &&\n+\tgit commit -a -m side &&\n+\tgit tag side\n+'\n+\n+test_expect_success 'git ls-files --deduplicate to show unique unmerged path' '\n+\ttest_must_fail git merge tip &&\n+\tgit ls-files --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit merge --abort\n+'\n+\n+test_expect_success 'git ls-files -d -m --deduplicate with different display options' '\n+\tgit reset --hard side &&\n+\ttest_must_fail git merge tip &&\n+\trm delete.txt &&\n+\tgit ls-files -d -m --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -t --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tC a.txt\n+\tC a.txt\n+\tC a.txt\n+\tR delete.txt\n+\tC delete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -c --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit merge --abort\n+'\n+\n+test_done\n-- \n2.30.0-491-g302c625a7b\n\n"},{"id":"415146","messageId":"8b02367a359e62d7721b9078ac8393a467d83724.1611485667.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v7.git.1611485667.gitgitgadget@gmail.com","subject":"[PATCH v7 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-24T10:54:25Z","receivedAt":"2021-01-24T10:55:36Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThis situation may occur in the original code: lstat() failed\nbut we use `&st` to feed ie_modified() later.\n\nTherefore, we can directly execute show_ce without the judgment of\nie_modified() when lstat() has failed.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n[jc: fixed misindented code]\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/ls-files.c | 13 ++++++++-----\n 1 file changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex c8eae899b82..1e264bd1329 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -335,7 +335,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n \t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n \t\t\tstruct stat st;\n-\t\t\tint err;\n+\t\t\tint stat_err;\n \n \t\t\tconstruct_fullname(&fullname, repo, ce);\n \n@@ -346,11 +346,14 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\t\tcontinue;\n \t\t\tif (ce_skip_worktree(ce))\n \t\t\t\tcontinue;\n-\t\t\terr = lstat(fullname.buf, &st);\n-\t\t\tif (show_deleted && err)\n+\t\t\tstat_err = lstat(fullname.buf, &st);\n+\t\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n+\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n+\t\t\tif (stat_err && show_deleted)\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified && ie_modified(repo->index, ce, &st, 0))\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\tif (show_modified &&\n+\t\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n+\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n \t\t}\n \t}\n \n-- \ngitgitgadget\n\n"},{"id":"415147","messageId":"pull.832.v7.git.1611485667.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v6.git.1611397210.gitgitgadget@gmail.com","subject":"[PATCH v7 0/3] builtin/ls-files.c:add git ls-file --dedup option","fromName":"阿德烈 via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-24T10:54:24Z","receivedAt":"2021-01-24T10:55:36Z","isPatch":true,"sender":{"key":"name:阿德烈","avatar":null},"body":"I am reading the source code of git ls-files and learned that git ls-files\nmay have duplicate files name when there are unmerged path in a branch merge\nor when different options are used at the same time. Users may fell confuse\nwhen they see these duplicate file names.\n\nAs Junio C Hamano said ,it have odd behaviour.\n\nTherefore, we can provide an additional option to git ls-files to delete\nthose repeated information.\n\nThis fixes https://github.com/gitgitgadget/git/issues/198\n\nThanks!\n\nZheNing Hu (3):\n  ls_files.c: bugfix for --deleted and --modified\n  ls_files.c: consolidate two for loops into one\n  ls-files.c: add --deduplicate option\n\n Documentation/git-ls-files.txt |  8 ++++\n builtin/ls-files.c             | 85 ++++++++++++++++++++--------------\n t/t3012-ls-files-dedup.sh      | 66 ++++++++++++++++++++++++++\n 3 files changed, 124 insertions(+), 35 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\n\nbase-commit: 6d3ef5b467eccd2769f1aa1c555d317d3c8dc707\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-832%2Fadlternative%2Fls-files-dedup-v7\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-832/adlternative/ls-files-dedup-v7\nPull-Request: https://github.com/gitgitgadget/git/pull/832\n\nRange-diff vs v6:\n\n 1:  fbc38ce9075 ! 1:  8b02367a359 ls_files.c: bugfix for --deleted and --modified\n     @@ Commit message\n          ie_modified() when lstat() has failed.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n     +    [jc: fixed misindented code]\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## builtin/ls-files.c ##\n      @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n 2:  3997d390883 ! 2:  f9d5e44d2c0 ls_files.c: consolidate two for loops into one\n     @@ Metadata\n       ## Commit message ##\n          ls_files.c: consolidate two for loops into one\n      \n     -    Refactor the two for loops into one,skip showing the ce if it\n     -    has the same name as the previously shown one, only when doing so\n     -    won't lose information.\n     +    This will make it easier to show only one entry per filename in the\n     +    next step.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n     +    [jc: corrected the log message]\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## builtin/ls-files.c ##\n      @@ builtin/ls-files.c: static void show_files(struct repository *repo, struct dir_struct *dir)\n 3:  07b603fd97c ! 3:  384f77a4c18 ls-files.c: add --deduplicate option\n     @@ Metadata\n       ## Commit message ##\n          ls-files.c: add --deduplicate option\n      \n     -    In order to provide users a better experience\n     -    when viewing information about files in the index\n     -    and the working tree, the `--deduplicate` option will suppress\n     -    some duplicate name under some conditions.\n     +    During a merge conflict, the name of a file may appear multiple\n     +    times in \"git ls-files\" output, once for each stage.  If you use\n     +    both `--delete` and `--modify` at the same time, the output may\n     +    mention a deleted file twice.\n      \n     -    In a merge conflict, one file name of \"git ls-files\" output may\n     -    appear multiple times. For example,now there is an unmerged path\n     -    `a.c`,`a.c` will appear three times in the output of\n     -    \"git ls-files\".We can use \"git ls-files --deduplicate\" to output\n     -    `a.c` only one time.(unless `--stage` or `--unmerged` is\n     -    used to view all the detailed information in the index)\n     +    When none of the '-t', '-u', or '-s' options is in use, these\n     +    duplicate entries do not add much value to the output.\n      \n     -    In addition, if you use both `--delete` and `--modify` at\n     -    the same time, The `--deduplicate` option\n     -    can also suppress file name output.\n     -\n     -    Additional instructions:\n     -    In order to display entries information,`deduplicate` suppresses\n     -    the output of duplicate file names, not the output of duplicate\n     -    entries information, so under the option of `-t`, `--stage`, `--unmerge`,\n     -    `--deduplicate` will have no effect.\n     +    Introduce a new '--deduplicate' option to suppress them.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n     +    [jc: extended doc and rewritten commit log]\n     +    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n      \n       ## Documentation/git-ls-files.txt ##\n      @@ Documentation/git-ls-files.txt: SYNOPSIS\n     @@ Documentation/git-ls-files.txt: OPTIONS\n       \tSee OUTPUT below for more information.\n       \n      +--deduplicate::\n     -+\tSuppress duplicate entries when there are unmerged paths in index\n     -+\tor `--deleted` and `--modified` are combined.\n     ++\tWhen only filenames are shown, suppress duplicates that may\n     ++\tcome from having multiple stages during a merge, or giving\n     ++\t`--deleted` and `--modified` option at the same time.\n     ++\tWhen any of the `-t`, `--unmerged`, or `--stage` option is\n     ++\tin use, this option has no effect.\n      +\n       -x <pattern>::\n       --exclude=<pattern>::\n\n-- \ngitgitgadget\n"},{"id":"415148","messageId":"f9d5e44d2c08b9e3d05a73b0a6e520ef7bb889c9.1611485667.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v7.git.1611485667.gitgitgadget@gmail.com","subject":"[PATCH v7 2/3] ls_files.c: consolidate two for loops into one","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-24T10:54:26Z","receivedAt":"2021-01-24T10:55:36Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nThis will make it easier to show only one entry per filename in the\nnext step.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n[jc: corrected the log message]\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/ls-files.c | 70 ++++++++++++++++++++--------------------------\n 1 file changed, 30 insertions(+), 40 deletions(-)\n\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 1e264bd1329..966c0ab0296 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -312,49 +312,39 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tif (show_killed)\n \t\t\tshow_killed_files(repo->index, dir);\n \t}\n-\tif (show_cached || show_stage) {\n-\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n-\n-\t\t\tconstruct_fullname(&fullname, repo, ce);\n-\n-\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (show_unmerged && !ce_stage(ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (ce->ce_flags & CE_UPDATE)\n-\t\t\t\tcontinue;\n-\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n-\t\t\t\tce_stage(ce) ? tag_unmerged :\n-\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n-\t\t\t\t tag_cached));\n-\t\t}\n-\t}\n-\tif (show_deleted || show_modified) {\n-\t\tfor (i = 0; i < repo->index->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = repo->index->cache[i];\n-\t\t\tstruct stat st;\n-\t\t\tint stat_err;\n+\tif (! (show_cached || show_stage || show_deleted || show_modified))\n+\t\treturn;\n+\tfor (i = 0; i < repo->index->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = repo->index->cache[i];\n+\t\tstruct stat st;\n+\t\tint stat_err;\n \n-\t\t\tconstruct_fullname(&fullname, repo, ce);\n+\t\tconstruct_fullname(&fullname, repo, ce);\n \n-\t\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n-\t\t\t    !ce_excluded(dir, repo->index, fullname.buf, ce))\n-\t\t\t\tcontinue;\n-\t\t\tif (ce->ce_flags & CE_UPDATE)\n-\t\t\t\tcontinue;\n-\t\t\tif (ce_skip_worktree(ce))\n-\t\t\t\tcontinue;\n-\t\t\tstat_err = lstat(fullname.buf, &st);\n-\t\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n-\t\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n-\t\t\tif (stat_err && show_deleted)\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n-\t\t\tif (show_modified &&\n-\t\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n-\t\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\tif ((dir->flags & DIR_SHOW_IGNORED) &&\n+\t\t\t!ce_excluded(dir, repo->index, fullname.buf, ce))\n+\t\t\tcontinue;\n+\t\tif (ce->ce_flags & CE_UPDATE)\n+\t\t\tcontinue;\n+\t\tif (show_cached || show_stage) {\n+\t\t\tif (!show_unmerged || ce_stage(ce))\n+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n+\t\t\t\t\tce_stage(ce) ? tag_unmerged :\n+\t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n+\t\t\t\t\t\ttag_cached));\n \t\t}\n+\t\tif (!show_deleted && !show_modified)\n+\t\t\tcontinue;\n+\t\tif (ce_skip_worktree(ce))\n+\t\t\tcontinue;\n+\t\tstat_err = lstat(fullname.buf, &st);\n+\t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n+\t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n+\t\tif (stat_err && show_deleted)\n+\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\tif (show_modified &&\n+\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n+\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n \t}\n \n \tstrbuf_release(&fullname);\n-- \ngitgitgadget\n\n"},{"id":"415149","messageId":"384f77a4c188456854bd86335e9bdc8018097a5f.1611485667.git.gitgitgadget@gmail.com","threadId":"54943","inReplyTo":"pull.832.v7.git.1611485667.gitgitgadget@gmail.com","subject":"[PATCH v7 3/3] ls-files.c: add --deduplicate option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-01-24T10:54:27Z","receivedAt":"2021-01-24T10:55:38Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nDuring a merge conflict, the name of a file may appear multiple\ntimes in \"git ls-files\" output, once for each stage.  If you use\nboth `--delete` and `--modify` at the same time, the output may\nmention a deleted file twice.\n\nWhen none of the '-t', '-u', or '-s' options is in use, these\nduplicate entries do not add much value to the output.\n\nIntroduce a new '--deduplicate' option to suppress them.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n[jc: extended doc and rewritten commit log]\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-ls-files.txt |  8 +++++\n builtin/ls-files.c             | 30 +++++++++++++---\n t/t3012-ls-files-dedup.sh      | 66 ++++++++++++++++++++++++++++++++++\n 3 files changed, 100 insertions(+), 4 deletions(-)\n create mode 100755 t/t3012-ls-files-dedup.sh\n\ndiff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\nindex cbcf5263dd0..60449a69b69 100644\n--- a/Documentation/git-ls-files.txt\n+++ b/Documentation/git-ls-files.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t\t(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*\n \t\t(-[c|d|o|i|s|u|k|m])*\n \t\t[--eol]\n+\t\t[--deduplicate]\n \t\t[-x <pattern>|--exclude=<pattern>]\n \t\t[-X <file>|--exclude-from=<file>]\n \t\t[--exclude-per-directory=<file>]\n@@ -81,6 +82,13 @@ OPTIONS\n \t\\0 line termination on output and do not quote filenames.\n \tSee OUTPUT below for more information.\n \n+--deduplicate::\n+\tWhen only filenames are shown, suppress duplicates that may\n+\tcome from having multiple stages during a merge, or giving\n+\t`--deleted` and `--modified` option at the same time.\n+\tWhen any of the `-t`, `--unmerged`, or `--stage` option is\n+\tin use, this option has no effect.\n+\n -x <pattern>::\n --exclude=<pattern>::\n \tSkip untracked files matching pattern.\ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex 966c0ab0296..fb9cf50d764 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -35,6 +35,7 @@ static int line_terminator = '\\n';\n static int debug_mode;\n static int show_eol;\n static int recurse_submodules;\n+static int skipping_duplicates;\n \n static const char *prefix;\n static int max_prefix_len;\n@@ -326,12 +327,14 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tcontinue;\n \t\tif (ce->ce_flags & CE_UPDATE)\n \t\t\tcontinue;\n-\t\tif (show_cached || show_stage) {\n-\t\t\tif (!show_unmerged || ce_stage(ce))\n+\t\tif ((show_cached || show_stage) &&\n+\t\t\t(!show_unmerged || ce_stage(ce))) {\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n \t\t\t\t\tce_stage(ce) ? tag_unmerged :\n \t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n \t\t\t\t\t\ttag_cached));\n+\t\t\tif (skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n \t\t}\n \t\tif (!show_deleted && !show_modified)\n \t\t\tcontinue;\n@@ -340,11 +343,27 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tstat_err = lstat(fullname.buf, &st);\n \t\tif (stat_err && (errno != ENOENT && errno != ENOTDIR))\n \t\t\terror_errno(\"cannot lstat '%s'\", fullname.buf);\n-\t\tif (stat_err && show_deleted)\n+\t\tif (stat_err && show_deleted) {\n \t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_removed);\n+\t\t\tif (skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n+\t\t}\n \t\tif (show_modified &&\n-\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0)))\n+\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0))) {\n \t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t\tif (skipping_duplicates)\n+\t\t\t\tgoto skip_to_next_name;\n+\t\t}\n+\t\tcontinue;\n+skip_to_next_name:\n+\t\t{\n+\t\t\tint j;\n+\t\t\tstruct cache_entry **cache = repo->index->cache;\n+\t\t\tfor (j = i + 1; j < repo->index->cache_nr; j++)\n+\t\t\t\tif (strcmp(ce->name, cache[j]->name))\n+\t\t\t\t\tbreak;\n+\t\t\ti = j - 1; /* compensate for outer for loop */\n+\t\t}\n \t}\n \n \tstrbuf_release(&fullname);\n@@ -571,6 +590,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n+\t\tOPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n \t\tOPT_END()\n \t};\n \n@@ -610,6 +630,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t * you also show the stage information.\n \t\t */\n \t\tshow_stage = 1;\n+\tif (show_tag || show_stage)\n+\t\tskipping_duplicates = 0;\n \tif (dir.exclude_per_dir)\n \t\texc_given = 1;\n \ndiff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh\nnew file mode 100755\nindex 00000000000..2682b1f43a6\n--- /dev/null\n+++ b/t/t3012-ls-files-dedup.sh\n@@ -0,0 +1,66 @@\n+#!/bin/sh\n+\n+test_description='git ls-files --deduplicate test'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\t>a.txt &&\n+\t>b.txt &&\n+\t>delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m base &&\n+\techo a >a.txt &&\n+\techo b >b.txt &&\n+\techo delete >delete.txt &&\n+\tgit add a.txt b.txt delete.txt &&\n+\tgit commit -m tip &&\n+\tgit tag tip &&\n+\tgit reset --hard HEAD^ &&\n+\techo change >a.txt &&\n+\tgit commit -a -m side &&\n+\tgit tag side\n+'\n+\n+test_expect_success 'git ls-files --deduplicate to show unique unmerged path' '\n+\ttest_must_fail git merge tip &&\n+\tgit ls-files --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit merge --abort\n+'\n+\n+test_expect_success 'git ls-files -d -m --deduplicate with different display options' '\n+\tgit reset --hard side &&\n+\ttest_must_fail git merge tip &&\n+\trm delete.txt &&\n+\tgit ls-files -d -m --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -t --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tC a.txt\n+\tC a.txt\n+\tC a.txt\n+\tR delete.txt\n+\tC delete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit ls-files -d -m -c --deduplicate >actual &&\n+\tcat >expect <<-\\EOF &&\n+\ta.txt\n+\tb.txt\n+\tdelete.txt\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit merge --abort\n+'\n+\n+test_done\n-- \ngitgitgadget\n"},{"id":"415182","messageId":"xmqqo8heyoti.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"8b02367a359e62d7721b9078ac8393a467d83724.1611485667.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v7 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-24T22:04:09Z","receivedAt":"2021-01-24T22:05:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: ZheNing Hu <adlternative@gmail.com>\n>\n> This situation may occur in the original code: lstat() failed\n> but we use `&st` to feed ie_modified() later.\n>\n> Therefore, we can directly execute show_ce without the judgment of\n> ie_modified() when lstat() has failed.\n>\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> [jc: fixed misindented code]\n\nI noticed that you reverted my fix in this version, when this is\ncompared with the one I sent last night.\n\nComparing the result of applying all three with what I sent last\nnight, this v7 looks worse (see below).  Let's discard this round\nand declare victory with what is already on 'seen'.\n\nThanks.\n\n\n---\n\ncomparison between what these three patches would produce (preimage)\nand what is on 'seen' (postimage)is shown here.\n\ndiff --git w/builtin/ls-files.c c/builtin/ls-files.c\nindex fb9cf50d76..f6f9e483b2 100644\n--- w/builtin/ls-files.c\n+++ c/builtin/ls-files.c\n@@ -313,7 +313,8 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tif (show_killed)\n \t\t\tshow_killed_files(repo->index, dir);\n \t}\n-\tif (! (show_cached || show_stage || show_deleted || show_modified))\n+\n+\tif (!(show_cached || show_stage || show_deleted || show_modified))\n \t\treturn;\n \tfor (i = 0; i < repo->index->cache_nr; i++) {\n \t\tconst struct cache_entry *ce = repo->index->cache[i];\n@@ -328,15 +329,16 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\tif (ce->ce_flags & CE_UPDATE)\n \t\t\tcontinue;\n \t\tif ((show_cached || show_stage) &&\n-\t\t\t(!show_unmerged || ce_stage(ce))) {\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n-\t\t\t\t\tce_stage(ce) ? tag_unmerged :\n-\t\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n-\t\t\t\t\t\ttag_cached));\n+\t\t    (!show_unmerged || ce_stage(ce))) {\n+\t\t\tshow_ce(repo, dir, ce, fullname.buf,\n+\t\t\t\tce_stage(ce) ? tag_unmerged :\n+\t\t\t\t(ce_skip_worktree(ce) ? tag_skip_worktree :\n+\t\t\t\t tag_cached));\n \t\t\tif (skipping_duplicates)\n \t\t\t\tgoto skip_to_next_name;\n \t\t}\n-\t\tif (!show_deleted && !show_modified)\n+\n+\t\tif (!(show_deleted || show_modified))\n \t\t\tcontinue;\n \t\tif (ce_skip_worktree(ce))\n \t\t\tcontinue;\n@@ -349,12 +351,13 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\t\tgoto skip_to_next_name;\n \t\t}\n \t\tif (show_modified &&\n-\t\t\t(stat_err || ie_modified(repo->index, ce, &st, 0))) {\n-\t\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n+\t\t    (stat_err || ie_modified(repo->index, ce, &st, 0))) {\n+\t\t\tshow_ce(repo, dir, ce, fullname.buf, tag_modified);\n \t\t\tif (skipping_duplicates)\n \t\t\t\tgoto skip_to_next_name;\n \t\t}\n \t\tcontinue;\n+\n skip_to_next_name:\n \t\t{\n \t\t\tint j;\n@@ -362,7 +365,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n \t\t\tfor (j = i + 1; j < repo->index->cache_nr; j++)\n \t\t\t\tif (strcmp(ce->name, cache[j]->name))\n \t\t\t\t\tbreak;\n-\t\t\ti = j - 1; /* compensate for outer for loop */\n+\t\t\ti = j - 1; /* compensate for the for loop */\n \t\t}\n \t}\n \n@@ -590,7 +593,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\t\tN_(\"pretend that paths removed since <tree-ish> are still present\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n-\t\tOPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n+\t\tOPT_BOOL(0, \"deduplicate\", &skipping_duplicates,\n+\t\t\t N_(\"suppress duplicate entries\")),\n \t\tOPT_END()\n \t};\n \n"},{"id":"415189","messageId":"CAOLTT8QgDeWmcX7y3QmQ7jyunziw4qn==L3BiJ81weMZtq8Obw@mail.gmail.com","threadId":"54943","inReplyTo":"xmqqo8heyoti.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v7 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"胡哲宁","fromEmail":"adlternative@gmail.com","sentAt":"2021-01-25T06:05:06Z","receivedAt":"2021-01-25T06:06:17Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"OK,I didn’t notice any formatting changes before.\n\nAm I free from this patch now?I should probably\nlook for other issues.\n\nJunio, thank you for all your patient help.\nI may often make some low-level mistakes.\nI am grateful.\n\nCheers.\n\nJunio C Hamano <gitster@pobox.com> 于2021年1月25日周一 上午6:04写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > This situation may occur in the original code: lstat() failed\n> > but we use `&st` to feed ie_modified() later.\n> >\n> > Therefore, we can directly execute show_ce without the judgment of\n> > ie_modified() when lstat() has failed.\n> >\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > [jc: fixed misindented code]\n>\n> I noticed that you reverted my fix in this version, when this is\n> compared with the one I sent last night.\n>\n> Comparing the result of applying all three with what I sent last\n> night, this v7 looks worse (see below).  Let's discard this round\n> and declare victory with what is already on 'seen'.\n>\n> Thanks.\n>\n>\n> ---\n>\n> comparison between what these three patches would produce (preimage)\n> and what is on 'seen' (postimage)is shown here.\n>\n> diff --git w/builtin/ls-files.c c/builtin/ls-files.c\n> index fb9cf50d76..f6f9e483b2 100644\n> --- w/builtin/ls-files.c\n> +++ c/builtin/ls-files.c\n> @@ -313,7 +313,8 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>                 if (show_killed)\n>                         show_killed_files(repo->index, dir);\n>         }\n> -       if (! (show_cached || show_stage || show_deleted || show_modified))\n> +\n> +       if (!(show_cached || show_stage || show_deleted || show_modified))\n>                 return;\n>         for (i = 0; i < repo->index->cache_nr; i++) {\n>                 const struct cache_entry *ce = repo->index->cache[i];\n> @@ -328,15 +329,16 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>                 if (ce->ce_flags & CE_UPDATE)\n>                         continue;\n>                 if ((show_cached || show_stage) &&\n> -                       (!show_unmerged || ce_stage(ce))) {\n> -                               show_ce(repo, dir, ce, fullname.buf,\n> -                                       ce_stage(ce) ? tag_unmerged :\n> -                                       (ce_skip_worktree(ce) ? tag_skip_worktree :\n> -                                               tag_cached));\n> +                   (!show_unmerged || ce_stage(ce))) {\n> +                       show_ce(repo, dir, ce, fullname.buf,\n> +                               ce_stage(ce) ? tag_unmerged :\n> +                               (ce_skip_worktree(ce) ? tag_skip_worktree :\n> +                                tag_cached));\n>                         if (skipping_duplicates)\n>                                 goto skip_to_next_name;\n>                 }\n> -               if (!show_deleted && !show_modified)\n> +\n> +               if (!(show_deleted || show_modified))\n>                         continue;\n>                 if (ce_skip_worktree(ce))\n>                         continue;\n> @@ -349,12 +351,13 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>                                 goto skip_to_next_name;\n>                 }\n>                 if (show_modified &&\n> -                       (stat_err || ie_modified(repo->index, ce, &st, 0))) {\n> -                               show_ce(repo, dir, ce, fullname.buf, tag_modified);\n> +                   (stat_err || ie_modified(repo->index, ce, &st, 0))) {\n> +                       show_ce(repo, dir, ce, fullname.buf, tag_modified);\n>                         if (skipping_duplicates)\n>                                 goto skip_to_next_name;\n>                 }\n>                 continue;\n> +\n>  skip_to_next_name:\n>                 {\n>                         int j;\n> @@ -362,7 +365,7 @@ static void show_files(struct repository *repo, struct dir_struct *dir)\n>                         for (j = i + 1; j < repo->index->cache_nr; j++)\n>                                 if (strcmp(ce->name, cache[j]->name))\n>                                         break;\n> -                       i = j - 1; /* compensate for outer for loop */\n> +                       i = j - 1; /* compensate for the for loop */\n>                 }\n>         }\n>\n> @@ -590,7 +593,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n>                         N_(\"pretend that paths removed since <tree-ish> are still present\")),\n>                 OPT__ABBREV(&abbrev),\n>                 OPT_BOOL(0, \"debug\", &debug_mode, N_(\"show debugging data\")),\n> -               OPT_BOOL(0,\"deduplicate\",&skipping_duplicates,N_(\"suppress duplicate entries\")),\n> +               OPT_BOOL(0, \"deduplicate\", &skipping_duplicates,\n> +                        N_(\"suppress duplicate entries\")),\n>                 OPT_END()\n>         };\n>\n"},{"id":"415229","messageId":"xmqqtur4ygzj.fsf@gitster.c.googlers.com","threadId":"54943","inReplyTo":"CAOLTT8QgDeWmcX7y3QmQ7jyunziw4qn==L3BiJ81weMZtq8Obw@mail.gmail.com","subject":"Re: [PATCH v7 1/3] ls_files.c: bugfix for --deleted and --modified","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-25T19:05:36Z","receivedAt":"2021-01-25T19:07:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"胡哲宁 <adlternative@gmail.com> writes:\n\n> OK,I didn’t notice any formatting changes before.\n>\n> Am I free from this patch now?I should probably\n> look for other issues.\n\nI think we are pretty much done with it.  Thanks for working on the\ntopic so patiently.\n\n"},{"id":"419703","messageId":"nycvar.QRO.7.76.6.2103191451460.57@tvgsbejvaqbjf.bet","threadId":"54943","inReplyTo":"xmqq8s8kalz3.fsf@gitster.c.googlers.com","subject":"GitGitGadget and `next`, was Re: [PATCH v5 3/3] ls-files.c: add --deduplicate option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-03-19T13:54:47Z","receivedAt":"2021-03-19T13:56:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nI just noticed that this still waited in my inbox for me to answer it.\n\nOn Fri, 22 Jan 2021, Junio C Hamano wrote:\n\n> Just being curious, but when a series hits 'next', would the way in\n> which the user interacts with GGG change?\n\nMy hunch is that we should probably tell new users (for who GitGitGadget\nnow uses the \"new user\" PR label) about the expectations of only adding\npatches on top (i.e. in a new PR), unless the branch gets kicked out of\n`next`.\n\n> With or without GGG, what is done on the local side is not all that\n> different---you build new commits on top without disturbing the commits\n> that are in 'next'. Then what?  Push it again (this time there is no\n> need to force) and submit the additional ones via `/submit`?\n\nGitGitGadget would send the entire patch series, which is probably not a\ngood idea.\n\nCiao,\nDscho\n"},{"id":"419725","messageId":"xmqqmtuzm2sf.fsf@gitster.g","threadId":"54943","inReplyTo":"nycvar.QRO.7.76.6.2103191451460.57@tvgsbejvaqbjf.bet","subject":"Re: GitGitGadget and `next`, was Re: [PATCH v5 3/3] ls-files.c: add --deduplicate option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-19T18:11:44Z","receivedAt":"2021-03-19T18:12:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> On Fri, 22 Jan 2021, Junio C Hamano wrote:\n>\n>> Just being curious, but when a series hits 'next', would the way in\n>> which the user interacts with GGG change?\n>\n> My hunch is that we should probably tell new users (for who GitGitGadget\n> now uses the \"new user\" PR label) about the expectations of only adding\n> patches on top (i.e. in a new PR), unless the branch gets kicked out of\n> `next`.\n>\n>> With or without GGG, what is done on the local side is not all that\n>> different---you build new commits on top without disturbing the commits\n>> that are in 'next'. Then what?  Push it again (this time there is no\n>> need to force) and submit the additional ones via `/submit`?\n>\n> GitGitGadget would send the entire patch series, which is probably not a\n> good idea.\n\nThanks for a clarification.\n\nWhile we are on the topic of GGG, if I may ask for a new feature or\ntwo (or perhaps such a feature already exists), it would be nice if\ncontributors are allowed to tweak who are CC'ed in the outgoing\npatch mail in various ways:\n\n - I may author a commit as <gitster@work.addre.ss> and make a pull\n   request on GitHub, but the <gitster@pobox.com> is the address\n   associated with the GitHub account making the pull request.  I\n   think GGG sends CC to the author (at work) as well as me, but I\n   may prefer to get correspondence on the patch at either one of my\n   addresses not both.  \"Mr GGG, please compute the CC list the\n   normal way, and drop this address from the CC list\" that I can\n   say when I say \"/submit\" might be a good way to do so.\n\n - Also, at \"/submit\" time, being able to say \"Also CC: these\n   addresses\" would be a good feature, without contaminating the\n   commit log message with CC: trailer lines.\n\nThanks.\n"}]}