]> git.ipfire.org Git - thirdparty/git.git/commitdiff
builtin/reflog: introduce subcommand to list reflogs
authorPatrick Steinhardt <ps@pks.im>
Wed, 21 Feb 2024 12:37:47 +0000 (13:37 +0100)
committerJunio C Hamano <gitster@pobox.com>
Wed, 21 Feb 2024 17:58:07 +0000 (09:58 -0800)
While the git-reflog(1) command has subcommands to show reflog entries
or check for reflog existence, it does not have any subcommands that
would allow the user to enumerate all existing reflogs. This makes it
quite hard to discover which reflogs a repository has. While this can
be worked around with the "files" backend by enumerating files in the
".git/logs" directory, users of the "reftable" backend don't enjoy such
a luxury.

Introduce a new subcommand `git reflog list` that lists all reflogs the
repository knows of to fill this gap.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-reflog.txt
builtin/reflog.c
t/t1410-reflog.sh

index ec64cbff4c6529d23fcf885ed5fa6f018404952f..a929c52982ff7629fdd32df1d008a0fb824847d0 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git reflog' [show] [<log-options>] [<ref>]
+'git reflog list'
 'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>]
        [--rewrite] [--updateref] [--stale-fix]
        [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]
@@ -39,6 +40,8 @@ actions, and in addition the `HEAD` reflog records branch switching.
 `git reflog show` is an alias for `git log -g --abbrev-commit
 --pretty=oneline`; see linkgit:git-log[1] for more information.
 
+The "list" subcommand lists all refs which have a corresponding reflog.
+
 The "expire" subcommand prunes older reflog entries. Entries older
 than `expire` time, or entries older than `expire-unreachable` time
 and not reachable from the current tip, are removed from the reflog.
index 3a0c4d432232bdc8680fa78d22baf35ac187c5b1..63cd4d8b2913b9ed4d3fe7684de2b7cad433d1fe 100644 (file)
@@ -7,11 +7,15 @@
 #include "wildmatch.h"
 #include "worktree.h"
 #include "reflog.h"
+#include "refs.h"
 #include "parse-options.h"
 
 #define BUILTIN_REFLOG_SHOW_USAGE \
        N_("git reflog [show] [<log-options>] [<ref>]")
 
+#define BUILTIN_REFLOG_LIST_USAGE \
+       N_("git reflog list")
+
 #define BUILTIN_REFLOG_EXPIRE_USAGE \
        N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \
           "                  [--rewrite] [--updateref] [--stale-fix]\n" \
@@ -29,6 +33,11 @@ static const char *const reflog_show_usage[] = {
        NULL,
 };
 
+static const char *const reflog_list_usage[] = {
+       BUILTIN_REFLOG_LIST_USAGE,
+       NULL,
+};
+
 static const char *const reflog_expire_usage[] = {
        BUILTIN_REFLOG_EXPIRE_USAGE,
        NULL
@@ -46,6 +55,7 @@ static const char *const reflog_exists_usage[] = {
 
 static const char *const reflog_usage[] = {
        BUILTIN_REFLOG_SHOW_USAGE,
+       BUILTIN_REFLOG_LIST_USAGE,
        BUILTIN_REFLOG_EXPIRE_USAGE,
        BUILTIN_REFLOG_DELETE_USAGE,
        BUILTIN_REFLOG_EXISTS_USAGE,
@@ -238,6 +248,29 @@ static int cmd_reflog_show(int argc, const char **argv, const char *prefix)
        return cmd_log_reflog(argc, argv, prefix);
 }
 
+static int show_reflog(const char *refname, void *cb_data UNUSED)
+{
+       printf("%s\n", refname);
+       return 0;
+}
+
+static int cmd_reflog_list(int argc, const char **argv, const char *prefix)
+{
+       struct option options[] = {
+               OPT_END()
+       };
+       struct ref_store *ref_store;
+
+       argc = parse_options(argc, argv, prefix, options, reflog_list_usage, 0);
+       if (argc)
+               return error(_("%s does not accept arguments: '%s'"),
+                            "list", argv[0]);
+
+       ref_store = get_main_ref_store(the_repository);
+
+       return refs_for_each_reflog(ref_store, show_reflog, NULL);
+}
+
 static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
 {
        struct cmd_reflog_expire_cb cmd = { 0 };
@@ -417,6 +450,7 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)
        parse_opt_subcommand_fn *fn = NULL;
        struct option options[] = {
                OPT_SUBCOMMAND("show", &fn, cmd_reflog_show),
+               OPT_SUBCOMMAND("list", &fn, cmd_reflog_list),
                OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire),
                OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete),
                OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists),
index d2f5f42e67440d856fd8f1752c75b20a16278e13..5bf883f1e363306a74b15df1ab02988564aa3208 100755 (executable)
@@ -436,4 +436,112 @@ test_expect_success 'empty reflog' '
        test_must_be_empty err
 '
 
+test_expect_success 'list reflogs' '
+       test_when_finished "rm -rf repo" &&
+       git init repo &&
+       (
+               cd repo &&
+               git reflog list >actual &&
+               test_must_be_empty actual &&
+
+               test_commit A &&
+               cat >expect <<-EOF &&
+               HEAD
+               refs/heads/main
+               EOF
+               git reflog list >actual &&
+               test_cmp expect actual &&
+
+               git branch b &&
+               cat >expect <<-EOF &&
+               HEAD
+               refs/heads/b
+               refs/heads/main
+               EOF
+               git reflog list >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'list reflogs with worktree' '
+       test_when_finished "rm -rf repo" &&
+       git init repo &&
+       (
+               cd repo &&
+
+               test_commit A &&
+               git worktree add wt &&
+               git -c core.logAllRefUpdates=always \
+                       update-ref refs/worktree/main HEAD &&
+               git -c core.logAllRefUpdates=always \
+                       update-ref refs/worktree/per-worktree HEAD &&
+               git -c core.logAllRefUpdates=always -C wt \
+                       update-ref refs/worktree/per-worktree HEAD &&
+               git -c core.logAllRefUpdates=always -C wt \
+                       update-ref refs/worktree/worktree HEAD &&
+
+               cat >expect <<-EOF &&
+               HEAD
+               refs/heads/main
+               refs/heads/wt
+               refs/worktree/main
+               refs/worktree/per-worktree
+               EOF
+               git reflog list >actual &&
+               test_cmp expect actual &&
+
+               cat >expect <<-EOF &&
+               HEAD
+               refs/heads/main
+               refs/heads/wt
+               refs/worktree/per-worktree
+               refs/worktree/worktree
+               EOF
+               git -C wt reflog list >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'reflog list returns error with additional args' '
+       cat >expect <<-EOF &&
+       error: list does not accept arguments: ${SQ}bogus${SQ}
+       EOF
+       test_must_fail git reflog list bogus 2>err &&
+       test_cmp expect err
+'
+
+test_expect_success 'reflog for symref with unborn target can be listed' '
+       test_when_finished "rm -rf repo" &&
+       git init repo &&
+       (
+               cd repo &&
+               test_commit A &&
+               git symbolic-ref HEAD refs/heads/unborn &&
+               cat >expect <<-EOF &&
+               HEAD
+               refs/heads/main
+               EOF
+               git reflog list >actual &&
+               test_cmp expect actual
+       )
+'
+
+test_expect_success 'reflog with invalid object ID can be listed' '
+       test_when_finished "rm -rf repo" &&
+       git init repo &&
+       (
+               cd repo &&
+               test_commit A &&
+               test-tool ref-store main update-ref msg refs/heads/missing \
+                       $(test_oid deadbeef) "$ZERO_OID" REF_SKIP_OID_VERIFICATION &&
+               cat >expect <<-EOF &&
+               HEAD
+               refs/heads/main
+               refs/heads/missing
+               EOF
+               git reflog list >actual &&
+               test_cmp expect actual
+       )
+'
+
 test_done