Karthik Nayak <karthik.188@xxxxxxxxx> writes: > While 'git-reflog(1)' currently allows users to expire reflogs and > delete individual entries, it lacks functionality to completely remove > reflogs for specific references. This becomes problematic in > repositories where reflogs are not needed but continue to accumulate > entries despite setting 'core.logAllRefUpdates=false'. > > Add a new 'drop' subcommand to git-reflog that allows users to delete > the entire reflog for a specified reference. Include an '--all' flag to > enable dropping all reflogs from all worktrees and an addon flag > '--single-worktree', to only drop all reflogs from the current worktree. > > While here, remove an extraneous newline in the file. > > Signed-off-by: Karthik Nayak <karthik.188@xxxxxxxxx> > --- > Documentation/git-reflog.adoc | 23 ++++++-- > builtin/reflog.c | 66 ++++++++++++++++++++++- > t/t1410-reflog.sh | 122 ++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 206 insertions(+), 5 deletions(-) > > diff --git a/Documentation/git-reflog.adoc b/Documentation/git-reflog.adoc > index a929c52982..b55c060569 100644 > --- a/Documentation/git-reflog.adoc > +++ b/Documentation/git-reflog.adoc > @@ -16,6 +16,7 @@ SYNOPSIS > [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...] > 'git reflog delete' [--rewrite] [--updateref] > [--dry-run | -n] [--verbose] <ref>@{<specifier>}... > +'git reflog drop' [--all [--single-worktree] | <refs>...] > 'git reflog exists' <ref> > > DESCRIPTION > @@ -48,10 +49,14 @@ and not reachable from the current tip, are removed from the reflog. > This is typically not used directly by end users -- instead, see > linkgit:git-gc[1]. > > -The "delete" subcommand deletes single entries from the reflog. Its > -argument must be an _exact_ entry (e.g. "`git reflog delete > -master@{2}`"). This subcommand is also typically not used directly by > -end users. > +The "delete" subcommand deletes single entries from the reflog, but > +not the reflog itself. Its argument must be an _exact_ entry (e.g. "`git > +reflog delete master@{2}`"). This subcommand is also typically not used > +directly by end users. > + > +The "drop" subcommand completely removes the reflog for the specified > +references. This is in contrast to "expire" and "delete", both of which > +can be used to delete reflog entries, but not the reflog itself. > > The "exists" subcommand checks whether a ref has a reflog. It exits > with zero status if the reflog exists, and non-zero status if it does > @@ -132,6 +137,16 @@ Options for `delete` > `--dry-run`, and `--verbose`, with the same meanings as when they are > used with `expire`. > > +Options for `drop` > +~~~~~~~~~~~~~~~~~~~~ > + > +--all:: > + Drop the reflogs of all references from all worktrees. > + > +--single-worktree:: > + By default when `--all` is specified, reflogs from all working > + trees are dropped. This option limits the processing to reflogs > + from the current working tree only. > > GIT > --- > diff --git a/builtin/reflog.c b/builtin/reflog.c > index 762719315e..a3652e69f1 100644 > --- a/builtin/reflog.c > +++ b/builtin/reflog.c > @@ -29,6 +29,9 @@ > #define BUILTIN_REFLOG_EXISTS_USAGE \ > N_("git reflog exists <ref>") > > +#define BUILTIN_REFLOG_DROP_USAGE \ > + N_("git reflog drop [--all [--single-worktree] | <refs>...]") > + > static const char *const reflog_show_usage[] = { > BUILTIN_REFLOG_SHOW_USAGE, > NULL, > @@ -54,11 +57,17 @@ static const char *const reflog_exists_usage[] = { > NULL, > }; > > +static const char *const reflog_drop_usage[] = { > + BUILTIN_REFLOG_DROP_USAGE, > + NULL, > +}; > + > static const char *const reflog_usage[] = { > BUILTIN_REFLOG_SHOW_USAGE, > BUILTIN_REFLOG_LIST_USAGE, > BUILTIN_REFLOG_EXPIRE_USAGE, > BUILTIN_REFLOG_DELETE_USAGE, > + BUILTIN_REFLOG_DROP_USAGE, > BUILTIN_REFLOG_EXISTS_USAGE, > NULL > }; > @@ -449,10 +458,64 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix, > refname); > } > > +static int cmd_reflog_drop(int argc, const char **argv, const char *prefix, > + struct repository *repo) > +{ > + int ret = 0, do_all = 0, single_worktree = 0; > + const struct option options[] = { > + OPT_BOOL(0, "all", &do_all, N_("drop the reflogs of all references")), > + OPT_BOOL(0, "single-worktree", &single_worktree, > + N_("drop reflogs from the current worktree only")), > + OPT_END() > + }; > + > + argc = parse_options(argc, argv, prefix, options, reflog_drop_usage, 0); > + > + if (argc && do_all) > + usage(_("references specified along with --all")); What is the intended behavior when both `--all` and `<refs>` are omitted? It seems nothing happens at the moment. And no error nor warning is printed, that feels a bit odd to me. Now, when you do `git reflog expire --expire=all` it also seems to be doing nothing at all. I also think this is weird. And I don't see any test coverage for `git reflog expire` without `--all`. But what is the expected behavior when you omit `--all` and `<refs>`? Should it give an error or warning? Should it use HEAD, just like `git reflog show` does? > + > + if (do_all) { > + struct worktree_reflogs collected = { > + .reflogs = STRING_LIST_INIT_DUP, > + }; > + struct string_list_item *item; > + struct worktree **worktrees, **p; > + > + worktrees = get_worktrees(); > + for (p = worktrees; *p; p++) { > + if (single_worktree && !(*p)->is_current) > + continue; > + collected.worktree = *p; > + refs_for_each_reflog(get_worktree_ref_store(*p), > + collect_reflog, &collected); > + } > + free_worktrees(worktrees); > + > + for_each_string_list_item(item, &collected.reflogs) > + ret |= refs_delete_reflog(get_main_ref_store(repo), > + item->string); > + string_list_clear(&collected.reflogs, 0); > + > + return ret; > + } > + > + for (int i = 0; i < argc; i++) { > + char *ref; > + if (!repo_dwim_log(repo, argv[i], strlen(argv[i]), NULL, &ref)) { > + ret |= error(_("reflog could not be found: '%s'"), argv[i]); > + continue; > + } > + > + ret |= refs_delete_reflog(get_main_ref_store(repo), ref); > + free(ref); > + } > + > + return ret; > +} > + > /* > * main "reflog" > */ > - > int cmd_reflog(int argc, > const char **argv, > const char *prefix, > @@ -465,6 +528,7 @@ int cmd_reflog(int argc, > OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), > OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete), > OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists), > + OPT_SUBCOMMAND("drop", &fn, cmd_reflog_drop), > OPT_END() > }; > > diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh > index 1f7249be76..42b501f163 100755 > --- a/t/t1410-reflog.sh > +++ b/t/t1410-reflog.sh > @@ -551,4 +551,126 @@ test_expect_success 'reflog with invalid object ID can be listed' ' > ) > ' > > +test_expect_success 'reflog drop non-existent ref' ' > + test_when_finished "rm -rf repo" && > + git init repo && > + ( > + cd repo && > + test_must_fail git reflog exists refs/heads/non-existent && > + test_must_fail git reflog drop refs/heads/non-existent 2>stderr && > + test_grep "error: reflog could not be found: ${SQ}refs/heads/non-existent${SQ}" stderr > + ) > +' > + > +test_expect_success 'reflog drop' ' > + test_when_finished "rm -rf repo" && > + git init repo && > + ( > + cd repo && > + test_commit A && > + test_commit_bulk --ref=refs/heads/branch 1 && > + git reflog exists refs/heads/main && > + git reflog exists refs/heads/branch && > + git reflog drop refs/heads/main && > + test_must_fail git reflog exists refs/heads/main && > + git reflog exists refs/heads/branch > + ) > +' > + > +test_expect_success 'reflog drop multiple references' ' > + test_when_finished "rm -rf repo" && > + git init repo && > + ( > + cd repo && > + test_commit A && > + test_commit_bulk --ref=refs/heads/branch 1 && > + git reflog exists refs/heads/main && > + git reflog exists refs/heads/branch && > + git reflog drop refs/heads/main refs/heads/branch && > + test_must_fail git reflog exists refs/heads/main && > + test_must_fail git reflog exists refs/heads/branch > + ) > +' > + > +test_expect_success 'reflog drop multiple references some non-existent' ' > + test_when_finished "rm -rf repo" && > + git init repo && > + ( > + cd repo && > + test_commit A && > + test_commit_bulk --ref=refs/heads/branch 1 && > + git reflog exists refs/heads/main && > + git reflog exists refs/heads/branch && > + test_must_fail git reflog exists refs/heads/non-existent && > + test_must_fail git reflog drop refs/heads/main refs/heads/non-existent refs/heads/branch 2>stderr && > + test_must_fail git reflog exists refs/heads/main && > + test_must_fail git reflog exists refs/heads/branch && > + test_must_fail git reflog exists refs/heads/non-existent && > + test_grep "error: reflog could not be found: ${SQ}refs/heads/non-existent${SQ}" stderr > + ) > +' > + > +test_expect_success 'reflog drop --all' ' > + test_when_finished "rm -rf repo" && > + git init repo && > + ( > + cd repo && > + test_commit A && > + test_commit_bulk --ref=refs/heads/branch 1 && > + git reflog exists refs/heads/main && > + git reflog exists refs/heads/branch && > + git reflog drop --all && > + test_must_fail git reflog exists refs/heads/main && > + test_must_fail git reflog exists refs/heads/branch Should we test output of `git reflog list`? > + ) > +' > + > +test_expect_success 'reflog drop --all multiple worktrees' ' > + test_when_finished "rm -rf repo" && > + test_when_finished "rm -rf wt" && > + git init repo && > + ( > + cd repo && > + test_commit A && > + git worktree add ../wt && > + test_commit_bulk -C ../wt --ref=refs/heads/branch 1 && > + git reflog exists refs/heads/main && > + git reflog exists refs/heads/branch && > + git reflog drop --all && > + test_must_fail git reflog exists refs/heads/main && > + test_must_fail git reflog exists refs/heads/branch Shall we test HEAD in both worktrees does not exists? > + ) > +' > + > +test_expect_success 'reflog drop --all --single-worktree' ' > + test_when_finished "rm -rf repo" && > + test_when_finished "rm -rf wt" && > + git init repo && > + ( > + cd repo && > + test_commit A && > + git worktree add ../wt && > + test_commit -C ../wt foobar && > + git reflog exists refs/heads/main && > + git reflog exists refs/heads/wt && > + test-tool ref-store worktree:wt reflog-exists HEAD && > + git reflog drop --all --single-worktree && > + test_must_fail git reflog exists refs/heads/main && > + test_must_fail git reflog exists refs/heads/wt && > + test_must_fail test-tool ref-store worktree:main reflog-exists HEAD && > + test-tool ref-store worktree:wt reflog-exists HEAD Naive question: why is `test-tool ref-store` used and not `git -C ../wt reflog exist`? > + ) > +' > + > +test_expect_success 'reflog drop --all with reference' ' > + test_when_finished "rm -rf repo" && > + git init repo && > + ( > + cd repo && > + test_commit A && > + test_must_fail git reflog drop --all refs/heads/main 2>stderr && > + test_grep "usage: references specified along with --all" stderr > + ) > +' > + > test_done > > -- > 2.48.1