]> git.ipfire.org Git - thirdparty/git.git/commitdiff
ls-files.c: add --deduplicate option
authorZheNing Hu <adlternative@gmail.com>
Sat, 23 Jan 2021 10:20:10 +0000 (10:20 +0000)
committerJunio C Hamano <gitster@pobox.com>
Sat, 23 Jan 2021 19:48:20 +0000 (11:48 -0800)
During a merge conflict, the name of a file may appear multiple
times in "git ls-files" output, once for each stage.  If you use
both `--delete` and `--modify` at the same time, the output may
mention a deleted file twice.

When none of the '-t', '-u', or '-s' options is in use, these
duplicate entries do not add much value to the output.

Introduce a new '--deduplicate' option to suppress them.

Signed-off-by: ZheNing Hu <adlternative@gmail.com>
[jc: extended doc and rewritten commit log]
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-ls-files.txt
builtin/ls-files.c
t/t3012-ls-files-dedup.sh [new file with mode: 0755]

index 0a3b5265b3457988bfad25b6b89c7622dd726760..6d11ab506b7c2e0990c93822970cd079e34b6c04 100644 (file)
@@ -13,6 +13,7 @@ SYNOPSIS
                (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*
                (-[c|d|o|i|s|u|k|m])*
                [--eol]
+               [--deduplicate]
                [-x <pattern>|--exclude=<pattern>]
                [-X <file>|--exclude-from=<file>]
                [--exclude-per-directory=<file>]
@@ -80,6 +81,13 @@ OPTIONS
        \0 line termination on output and do not quote filenames.
        See OUTPUT below for more information.
 
+--deduplicate::
+       When only filenames are shown, suppress duplicates that may
+       come from having multiple stages during a merge, or giving
+       `--deleted` and `--modified` option at the same time.
+       When any of the `-t`, `--unmerged`, or `--stage` option is
+       in use, this option has no effect.
+
 -x <pattern>::
 --exclude=<pattern>::
        Skip untracked files matching pattern.
index e94d724aff79c6c93d5a2245b2e09726795901c9..f6f9e483b27e183e29e9c409669c6b23d5590cfc 100644 (file)
@@ -35,6 +35,7 @@ static int line_terminator = '\n';
 static int debug_mode;
 static int show_eol;
 static int recurse_submodules;
+static int skipping_duplicates;
 
 static const char *prefix;
 static int max_prefix_len;
@@ -328,11 +329,14 @@ static void show_files(struct repository *repo, struct dir_struct *dir)
                if (ce->ce_flags & CE_UPDATE)
                        continue;
                if ((show_cached || show_stage) &&
-                   (!show_unmerged || ce_stage(ce)))
+                   (!show_unmerged || ce_stage(ce))) {
                        show_ce(repo, dir, ce, fullname.buf,
                                ce_stage(ce) ? tag_unmerged :
                                (ce_skip_worktree(ce) ? tag_skip_worktree :
                                 tag_cached));
+                       if (skipping_duplicates)
+                               goto skip_to_next_name;
+               }
 
                if (!(show_deleted || show_modified))
                        continue;
@@ -341,11 +345,28 @@ static void show_files(struct repository *repo, struct dir_struct *dir)
                stat_err = lstat(fullname.buf, &st);
                if (stat_err && (errno != ENOENT && errno != ENOTDIR))
                        error_errno("cannot lstat '%s'", fullname.buf);
-               if (stat_err && show_deleted)
+               if (stat_err && show_deleted) {
                        show_ce(repo, dir, ce, fullname.buf, tag_removed);
+                       if (skipping_duplicates)
+                               goto skip_to_next_name;
+               }
                if (show_modified &&
-                   (stat_err || ie_modified(repo->index, ce, &st, 0)))
+                   (stat_err || ie_modified(repo->index, ce, &st, 0))) {
                        show_ce(repo, dir, ce, fullname.buf, tag_modified);
+                       if (skipping_duplicates)
+                               goto skip_to_next_name;
+               }
+               continue;
+
+skip_to_next_name:
+               {
+                       int j;
+                       struct cache_entry **cache = repo->index->cache;
+                       for (j = i + 1; j < repo->index->cache_nr; j++)
+                               if (strcmp(ce->name, cache[j]->name))
+                                       break;
+                       i = j - 1; /* compensate for the for loop */
+               }
        }
 
        strbuf_release(&fullname);
@@ -572,6 +593,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                        N_("pretend that paths removed since <tree-ish> are still present")),
                OPT__ABBREV(&abbrev),
                OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")),
+               OPT_BOOL(0, "deduplicate", &skipping_duplicates,
+                        N_("suppress duplicate entries")),
                OPT_END()
        };
 
@@ -611,6 +634,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                 * you also show the stage information.
                 */
                show_stage = 1;
+       if (show_tag || show_stage)
+               skipping_duplicates = 0;
        if (dir.exclude_per_dir)
                exc_given = 1;
 
diff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh
new file mode 100755 (executable)
index 0000000..2682b1f
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+test_description='git ls-files --deduplicate test'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       >a.txt &&
+       >b.txt &&
+       >delete.txt &&
+       git add a.txt b.txt delete.txt &&
+       git commit -m base &&
+       echo a >a.txt &&
+       echo b >b.txt &&
+       echo delete >delete.txt &&
+       git add a.txt b.txt delete.txt &&
+       git commit -m tip &&
+       git tag tip &&
+       git reset --hard HEAD^ &&
+       echo change >a.txt &&
+       git commit -a -m side &&
+       git tag side
+'
+
+test_expect_success 'git ls-files --deduplicate to show unique unmerged path' '
+       test_must_fail git merge tip &&
+       git ls-files --deduplicate >actual &&
+       cat >expect <<-\EOF &&
+       a.txt
+       b.txt
+       delete.txt
+       EOF
+       test_cmp expect actual &&
+       git merge --abort
+'
+
+test_expect_success 'git ls-files -d -m --deduplicate with different display options' '
+       git reset --hard side &&
+       test_must_fail git merge tip &&
+       rm delete.txt &&
+       git ls-files -d -m --deduplicate >actual &&
+       cat >expect <<-\EOF &&
+       a.txt
+       delete.txt
+       EOF
+       test_cmp expect actual &&
+       git ls-files -d -m -t --deduplicate >actual &&
+       cat >expect <<-\EOF &&
+       C a.txt
+       C a.txt
+       C a.txt
+       R delete.txt
+       C delete.txt
+       EOF
+       test_cmp expect actual &&
+       git ls-files -d -m -c --deduplicate >actual &&
+       cat >expect <<-\EOF &&
+       a.txt
+       b.txt
+       delete.txt
+       EOF
+       test_cmp expect actual &&
+       git merge --abort
+'
+
+test_done