Message ID | 20250314-493-add-command-to-purge-reflog-entries-v3-2-c24e23a6146d@gmail.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | reflog: implement subcommand to drop reflogs | expand |
On Fri, Mar 14, 2025 at 9:41 AM Karthik Nayak <karthik.188@gmail.com> wrote: > +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. It seems to me that "--current-worktree" would have been clearer than "--single-worktree", but I understand that it would have been confusing to have a different name for basically the same option in `git reflog expire` and `git reflog drop`. > + argc = parse_options(argc, argv, prefix, options, reflog_drop_usage, 0); > + > + if (argc && do_all) > + usage(_("references specified along with --all")); > + > + 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) It looks like 'single_worktree' is only used here. This means that if a user forgets to add --all and only uses --single-worktree, nothing will happen and it seems to me that the command will exit with code 0. Even if `git reflog expire` already works like that, I think this is a bit unfortunate. Otherwise this patch series looks very well done to me. Thanks!
Karthik Nayak <karthik.188@gmail.com> 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@gmail.com> > --- > 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
Christian Couder <christian.couder@gmail.com> writes: > It looks like 'single_worktree' is only used here. This means that if > a user forgets to add --all and only uses --single-worktree, nothing > will happen and it seems to me that the command will exit with code 0. > Even if `git reflog expire` already works like that, I think this is a > bit unfortunate. > > Otherwise this patch series looks very well done to me. In the thread Toon too seems to have noticed the same "what if there is no --all and --single-worktree is given?" gotcha. Together with the "current would be better name than single", we can consider that these funnies are to be "consistent" with the "expire" thing, and I am OK to see us move on. An alternative is to "fix" the behaviour and naming of the "expire" first, and then use the same improved behaviour and naming when adding "drop" as a new thing. Thanks.
On Tue, Mar 18, 2025 at 6:44 PM Junio C Hamano <gitster@pobox.com> wrote: > > Christian Couder <christian.couder@gmail.com> writes: > > > It looks like 'single_worktree' is only used here. This means that if > > a user forgets to add --all and only uses --single-worktree, nothing > > will happen and it seems to me that the command will exit with code 0. > > Even if `git reflog expire` already works like that, I think this is a > > bit unfortunate. > > > > Otherwise this patch series looks very well done to me. > > In the thread Toon too seems to have noticed the same "what if there > is no --all and --single-worktree is given?" gotcha. Together with > the "current would be better name than single", we can consider that > these funnies are to be "consistent" with the "expire" thing, and I > am OK to see us move on. I am OK with moving on too. We can "fix" the behavior and naming later in a dedicated separate patch series. > An alternative is to "fix" the behaviour > and naming of the "expire" first, and then use the same improved > behaviour and naming when adding "drop" as a new thing. I would be OK with that too. Thanks.
Christian Couder <christian.couder@gmail.com> writes: > On Tue, Mar 18, 2025 at 6:44 PM Junio C Hamano <gitster@pobox.com> wrote: >> >> Christian Couder <christian.couder@gmail.com> writes: >> >> > It looks like 'single_worktree' is only used here. This means that if >> > a user forgets to add --all and only uses --single-worktree, nothing >> > will happen and it seems to me that the command will exit with code 0. >> > Even if `git reflog expire` already works like that, I think this is a >> > bit unfortunate. >> > >> > Otherwise this patch series looks very well done to me. >> >> In the thread Toon too seems to have noticed the same "what if there >> is no --all and --single-worktree is given?" gotcha. Together with >> the "current would be better name than single", we can consider that >> these funnies are to be "consistent" with the "expire" thing, and I >> am OK to see us move on. > > I am OK with moving on too. We can "fix" the behavior and naming later > in a dedicated separate patch series. > Seems good, let's do that then. I'll see if I can follow up but this could also be #leftoverbits if someone wants to pick it up! >> An alternative is to "fix" the behaviour >> and naming of the "expire" first, and then use the same improved >> behaviour and naming when adding "drop" as a new thing. > > I would be OK with that too. > > Thanks.
Toon Claes <toon@iotcl.com> writes: > Karthik Nayak <karthik.188@gmail.com> writes: > [snip] >> +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? > As discussed in the other thread [1], ideally this should be raised as an error. I'm leaving it for now. [snip] >> + >> +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`? > I don't see why, we're concerned with individual reflogs and 'exists' help check against those individual reflogs. >> + ) >> +' >> + >> +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? > I think it would be a good addition, but I'm not sure if its worthy of a re-roll. >> + ) >> +' >> + >> +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`? > That should work too :) >> + ) >> +' >> + >> +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 [1]: CAOLa=ZSj11TSTs6CywSX1Q9AAfW28zssS2yrGf8PmBOgd06Etg@mail.gmail.com
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")); + + 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 + ) +' + +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 + ) +' + +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 + ) +' + +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
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@gmail.com> --- Documentation/git-reflog.adoc | 23 ++++++-- builtin/reflog.c | 66 ++++++++++++++++++++++- t/t1410-reflog.sh | 122 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+), 5 deletions(-)