builtin/reflog: introduce subcommand to list reflogs
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>
Patrick Steinhardt committed
Feb 21, 2024 at 13:37 UTC
d699d15c328b03fd822d3950f7ed76debef02c26
3 files changed
+145
Documentation/git-reflog.txt
+3
@@ -10,6 +10,7 @@ SYNOPSIS
10
--------
11
[verse]
12
'git reflog' [show] [<log-options>] [<ref>]
13
+'git reflog list'
14
'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>]
15
[--rewrite] [--updateref] [--stale-fix]
16
[--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]
@@ -39,6 +40,8 @@ actions, and in addition the `HEAD` reflog records branch switching.
40
`git reflog show` is an alias for `git log -g --abbrev-commit
41
--pretty=oneline`; see linkgit:git-log[1] for more information.
42
43
+The "list" subcommand lists all refs which have a corresponding reflog.
44
+
45
The "expire" subcommand prunes older reflog entries. Entries older
46
than `expire` time, or entries older than `expire-unreachable` time
47
and not reachable from the current tip, are removed from the reflog.
builtin/reflog.c
+34
@@ -7,11 +7,15 @@
7
#include "wildmatch.h"
8
#include "worktree.h"
9
#include "reflog.h"
10
+#include "refs.h"
11
#include "parse-options.h"
12
13
#define BUILTIN_REFLOG_SHOW_USAGE \
14
N_("git reflog [show] [<log-options>] [<ref>]")
15
16
+#define BUILTIN_REFLOG_LIST_USAGE \
17
+ N_("git reflog list")
18
+
19
#define BUILTIN_REFLOG_EXPIRE_USAGE \
20
N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \
21
" [--rewrite] [--updateref] [--stale-fix]\n" \
@@ -29,6 +33,11 @@ static const char *const reflog_show_usage[] = {
33
NULL,
34
};
35
36
+static const char *const reflog_list_usage[] = {
37
+ BUILTIN_REFLOG_LIST_USAGE,
38
+ NULL,
39
+};
40
+
41
static const char *const reflog_expire_usage[] = {
42
BUILTIN_REFLOG_EXPIRE_USAGE,
43
NULL
@@ -46,6 +55,7 @@ static const char *const reflog_exists_usage[] = {
55
56
static const char *const reflog_usage[] = {
57
BUILTIN_REFLOG_SHOW_USAGE,
58
+ BUILTIN_REFLOG_LIST_USAGE,
59
BUILTIN_REFLOG_EXPIRE_USAGE,
60
BUILTIN_REFLOG_DELETE_USAGE,
61
BUILTIN_REFLOG_EXISTS_USAGE,
@@ -238,6 +248,29 @@ static int cmd_reflog_show(int argc, const char **argv, const char *prefix)
248
return cmd_log_reflog(argc, argv, prefix);
249
}
250
251
+static int show_reflog(const char *refname, void *cb_data UNUSED)
252
+{
253
+ printf("%s\n", refname);
254
+ return 0;
255
+}
256
+
257
+static int cmd_reflog_list(int argc, const char **argv, const char *prefix)
258
+{
259
+ struct option options[] = {
260
+ OPT_END()
261
+ };
262
+ struct ref_store *ref_store;
263
+
264
+ argc = parse_options(argc, argv, prefix, options, reflog_list_usage, 0);
265
+ if (argc)
266
+ return error(_("%s does not accept arguments: '%s'"),
267
+ "list", argv[0]);
268
+
269
+ ref_store = get_main_ref_store(the_repository);
270
+
271
+ return refs_for_each_reflog(ref_store, show_reflog, NULL);
272
+}
273
+
274
static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
275
{
276
struct cmd_reflog_expire_cb cmd = { 0 };
@@ -417,6 +450,7 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)
450
parse_opt_subcommand_fn *fn = NULL;
451
struct option options[] = {
452
OPT_SUBCOMMAND("show", &fn, cmd_reflog_show),
453
+ OPT_SUBCOMMAND("list", &fn, cmd_reflog_list),
454
OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire),
455
OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete),
456
OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists),
t/t1410-reflog.sh
+108
@@ -436,4 +436,112 @@ test_expect_success 'empty reflog' '
436
test_must_be_empty err
437
'
438
439
+test_expect_success 'list reflogs' '
440
+ test_when_finished "rm -rf repo" &&
441
+ git init repo &&
442
+ (
443
+ cd repo &&
444
+ git reflog list >actual &&
445
+ test_must_be_empty actual &&
446
+
447
+ test_commit A &&
448
+ cat >expect <<-EOF &&
449
+ HEAD
450
+ refs/heads/main
451
+ EOF
452
+ git reflog list >actual &&
453
+ test_cmp expect actual &&
454
+
455
+ git branch b &&
456
+ cat >expect <<-EOF &&
457
+ HEAD
458
+ refs/heads/b
459
+ refs/heads/main
460
+ EOF
461
+ git reflog list >actual &&
462
+ test_cmp expect actual
463
+ )
464
+'
465
+
466
+test_expect_success 'list reflogs with worktree' '
467
+ test_when_finished "rm -rf repo" &&
468
+ git init repo &&
469
+ (
470
+ cd repo &&
471
+
472
+ test_commit A &&
473
+ git worktree add wt &&
474
+ git -c core.logAllRefUpdates=always \
475
+ update-ref refs/worktree/main HEAD &&
476
+ git -c core.logAllRefUpdates=always \
477
+ update-ref refs/worktree/per-worktree HEAD &&
478
+ git -c core.logAllRefUpdates=always -C wt \
479
+ update-ref refs/worktree/per-worktree HEAD &&
480
+ git -c core.logAllRefUpdates=always -C wt \
481
+ update-ref refs/worktree/worktree HEAD &&
482
+
483
+ cat >expect <<-EOF &&
484
+ HEAD
485
+ refs/heads/main
486
+ refs/heads/wt
487
+ refs/worktree/main
488
+ refs/worktree/per-worktree
489
+ EOF
490
+ git reflog list >actual &&
491
+ test_cmp expect actual &&
492
+
493
+ cat >expect <<-EOF &&
494
+ HEAD
495
+ refs/heads/main
496
+ refs/heads/wt
497
+ refs/worktree/per-worktree
498
+ refs/worktree/worktree
499
+ EOF
500
+ git -C wt reflog list >actual &&
501
+ test_cmp expect actual
502
+ )
503
+'
504
+
505
+test_expect_success 'reflog list returns error with additional args' '
506
+ cat >expect <<-EOF &&
507
+ error: list does not accept arguments: ${SQ}bogus${SQ}
508
+ EOF
509
+ test_must_fail git reflog list bogus 2>err &&
510
+ test_cmp expect err
511
+'
512
+
513
+test_expect_success 'reflog for symref with unborn target can be listed' '
514
+ test_when_finished "rm -rf repo" &&
515
+ git init repo &&
516
+ (
517
+ cd repo &&
518
+ test_commit A &&
519
+ git symbolic-ref HEAD refs/heads/unborn &&
520
+ cat >expect <<-EOF &&
521
+ HEAD
522
+ refs/heads/main
523
+ EOF
524
+ git reflog list >actual &&
525
+ test_cmp expect actual
526
+ )
527
+'
528
+
529
+test_expect_success 'reflog with invalid object ID can be listed' '
530
+ test_when_finished "rm -rf repo" &&
531
+ git init repo &&
532
+ (
533
+ cd repo &&
534
+ test_commit A &&
535
+ test-tool ref-store main update-ref msg refs/heads/missing \
536
+ $(test_oid deadbeef) "$ZERO_OID" REF_SKIP_OID_VERIFICATION &&
537
+ cat >expect <<-EOF &&
538
+ HEAD
539
+ refs/heads/main
540
+ refs/heads/missing
541
+ EOF
542
+ git reflog list >actual &&
543
+ test_cmp expect actual
544
+ )
545
+'
546
+
547
test_done