Coverage Report

Created: 2024-09-16 06:10

/src/git/builtin/reflog.c
Line
Count
Source (jump to first uncovered line)
1
#include "builtin.h"
2
#include "config.h"
3
#include "gettext.h"
4
#include "repository.h"
5
#include "revision.h"
6
#include "reachable.h"
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" \
22
     "                  [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]")
23
24
#define BUILTIN_REFLOG_DELETE_USAGE \
25
  N_("git reflog delete [--rewrite] [--updateref]\n" \
26
     "                  [--dry-run | -n] [--verbose] <ref>@{<specifier>}...")
27
28
#define BUILTIN_REFLOG_EXISTS_USAGE \
29
  N_("git reflog exists <ref>")
30
31
static const char *const reflog_show_usage[] = {
32
  BUILTIN_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
44
};
45
46
static const char *const reflog_delete_usage[] = {
47
  BUILTIN_REFLOG_DELETE_USAGE,
48
  NULL
49
};
50
51
static const char *const reflog_exists_usage[] = {
52
  BUILTIN_REFLOG_EXISTS_USAGE,
53
  NULL,
54
};
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,
62
  NULL
63
};
64
65
static timestamp_t default_reflog_expire;
66
static timestamp_t default_reflog_expire_unreachable;
67
68
struct worktree_reflogs {
69
  struct worktree *worktree;
70
  struct string_list reflogs;
71
};
72
73
static int collect_reflog(const char *ref, void *cb_data)
74
0
{
75
0
  struct worktree_reflogs *cb = cb_data;
76
0
  struct worktree *worktree = cb->worktree;
77
0
  struct strbuf newref = STRBUF_INIT;
78
79
  /*
80
   * Avoid collecting the same shared ref multiple times because
81
   * they are available via all worktrees.
82
   */
83
0
  if (!worktree->is_current &&
84
0
      parse_worktree_ref(ref, NULL, NULL, NULL) == REF_WORKTREE_SHARED)
85
0
    return 0;
86
87
0
  strbuf_worktree_ref(worktree, &newref, ref);
88
0
  string_list_append_nodup(&cb->reflogs, strbuf_detach(&newref, NULL));
89
90
0
  return 0;
91
0
}
92
93
static struct reflog_expire_cfg {
94
  struct reflog_expire_cfg *next;
95
  timestamp_t expire_total;
96
  timestamp_t expire_unreachable;
97
  char pattern[FLEX_ARRAY];
98
} *reflog_expire_cfg, **reflog_expire_cfg_tail;
99
100
static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
101
0
{
102
0
  struct reflog_expire_cfg *ent;
103
104
0
  if (!reflog_expire_cfg_tail)
105
0
    reflog_expire_cfg_tail = &reflog_expire_cfg;
106
107
0
  for (ent = reflog_expire_cfg; ent; ent = ent->next)
108
0
    if (!xstrncmpz(ent->pattern, pattern, len))
109
0
      return ent;
110
111
0
  FLEX_ALLOC_MEM(ent, pattern, pattern, len);
112
0
  *reflog_expire_cfg_tail = ent;
113
0
  reflog_expire_cfg_tail = &(ent->next);
114
0
  return ent;
115
0
}
116
117
/* expiry timer slot */
118
0
#define EXPIRE_TOTAL   01
119
0
#define EXPIRE_UNREACH 02
120
121
static int reflog_expire_config(const char *var, const char *value,
122
        const struct config_context *ctx, void *cb)
123
0
{
124
0
  const char *pattern, *key;
125
0
  size_t pattern_len;
126
0
  timestamp_t expire;
127
0
  int slot;
128
0
  struct reflog_expire_cfg *ent;
129
130
0
  if (parse_config_key(var, "gc", &pattern, &pattern_len, &key) < 0)
131
0
    return git_default_config(var, value, ctx, cb);
132
133
0
  if (!strcmp(key, "reflogexpire")) {
134
0
    slot = EXPIRE_TOTAL;
135
0
    if (git_config_expiry_date(&expire, var, value))
136
0
      return -1;
137
0
  } else if (!strcmp(key, "reflogexpireunreachable")) {
138
0
    slot = EXPIRE_UNREACH;
139
0
    if (git_config_expiry_date(&expire, var, value))
140
0
      return -1;
141
0
  } else
142
0
    return git_default_config(var, value, ctx, cb);
143
144
0
  if (!pattern) {
145
0
    switch (slot) {
146
0
    case EXPIRE_TOTAL:
147
0
      default_reflog_expire = expire;
148
0
      break;
149
0
    case EXPIRE_UNREACH:
150
0
      default_reflog_expire_unreachable = expire;
151
0
      break;
152
0
    }
153
0
    return 0;
154
0
  }
155
156
0
  ent = find_cfg_ent(pattern, pattern_len);
157
0
  if (!ent)
158
0
    return -1;
159
0
  switch (slot) {
160
0
  case EXPIRE_TOTAL:
161
0
    ent->expire_total = expire;
162
0
    break;
163
0
  case EXPIRE_UNREACH:
164
0
    ent->expire_unreachable = expire;
165
0
    break;
166
0
  }
167
0
  return 0;
168
0
}
169
170
static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, const char *ref)
171
0
{
172
0
  struct reflog_expire_cfg *ent;
173
174
0
  if (cb->explicit_expiry == (EXPIRE_TOTAL|EXPIRE_UNREACH))
175
0
    return; /* both given explicitly -- nothing to tweak */
176
177
0
  for (ent = reflog_expire_cfg; ent; ent = ent->next) {
178
0
    if (!wildmatch(ent->pattern, ref, 0)) {
179
0
      if (!(cb->explicit_expiry & EXPIRE_TOTAL))
180
0
        cb->expire_total = ent->expire_total;
181
0
      if (!(cb->explicit_expiry & EXPIRE_UNREACH))
182
0
        cb->expire_unreachable = ent->expire_unreachable;
183
0
      return;
184
0
    }
185
0
  }
186
187
  /*
188
   * If unconfigured, make stash never expire
189
   */
190
0
  if (!strcmp(ref, "refs/stash")) {
191
0
    if (!(cb->explicit_expiry & EXPIRE_TOTAL))
192
0
      cb->expire_total = 0;
193
0
    if (!(cb->explicit_expiry & EXPIRE_UNREACH))
194
0
      cb->expire_unreachable = 0;
195
0
    return;
196
0
  }
197
198
  /* Nothing matched -- use the default value */
199
0
  if (!(cb->explicit_expiry & EXPIRE_TOTAL))
200
0
    cb->expire_total = default_reflog_expire;
201
0
  if (!(cb->explicit_expiry & EXPIRE_UNREACH))
202
0
    cb->expire_unreachable = default_reflog_expire_unreachable;
203
0
}
204
205
static int expire_unreachable_callback(const struct option *opt,
206
         const char *arg,
207
         int unset)
208
0
{
209
0
  struct cmd_reflog_expire_cb *cmd = opt->value;
210
211
0
  BUG_ON_OPT_NEG(unset);
212
213
0
  if (parse_expiry_date(arg, &cmd->expire_unreachable))
214
0
    die(_("invalid timestamp '%s' given to '--%s'"),
215
0
        arg, opt->long_name);
216
217
0
  cmd->explicit_expiry |= EXPIRE_UNREACH;
218
0
  return 0;
219
0
}
220
221
static int expire_total_callback(const struct option *opt,
222
         const char *arg,
223
         int unset)
224
0
{
225
0
  struct cmd_reflog_expire_cb *cmd = opt->value;
226
227
0
  BUG_ON_OPT_NEG(unset);
228
229
0
  if (parse_expiry_date(arg, &cmd->expire_total))
230
0
    die(_("invalid timestamp '%s' given to '--%s'"),
231
0
        arg, opt->long_name);
232
233
0
  cmd->explicit_expiry |= EXPIRE_TOTAL;
234
0
  return 0;
235
0
}
236
237
static int cmd_reflog_show(int argc, const char **argv, const char *prefix)
238
0
{
239
0
  struct option options[] = {
240
0
    OPT_END()
241
0
  };
242
243
0
  parse_options(argc, argv, prefix, options, reflog_show_usage,
244
0
          PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 |
245
0
          PARSE_OPT_KEEP_UNKNOWN_OPT);
246
247
0
  return cmd_log_reflog(argc, argv, prefix);
248
0
}
249
250
static int show_reflog(const char *refname, void *cb_data UNUSED)
251
0
{
252
0
  printf("%s\n", refname);
253
0
  return 0;
254
0
}
255
256
static int cmd_reflog_list(int argc, const char **argv, const char *prefix)
257
0
{
258
0
  struct option options[] = {
259
0
    OPT_END()
260
0
  };
261
0
  struct ref_store *ref_store;
262
263
0
  argc = parse_options(argc, argv, prefix, options, reflog_list_usage, 0);
264
0
  if (argc)
265
0
    return error(_("%s does not accept arguments: '%s'"),
266
0
           "list", argv[0]);
267
268
0
  ref_store = get_main_ref_store(the_repository);
269
270
0
  return refs_for_each_reflog(ref_store, show_reflog, NULL);
271
0
}
272
273
static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
274
0
{
275
0
  struct cmd_reflog_expire_cb cmd = { 0 };
276
0
  timestamp_t now = time(NULL);
277
0
  int i, status, do_all, single_worktree = 0;
278
0
  unsigned int flags = 0;
279
0
  int verbose = 0;
280
0
  reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;
281
0
  const struct option options[] = {
282
0
    OPT_BIT('n', "dry-run", &flags, N_("do not actually prune any entries"),
283
0
      EXPIRE_REFLOGS_DRY_RUN),
284
0
    OPT_BIT(0, "rewrite", &flags,
285
0
      N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"),
286
0
      EXPIRE_REFLOGS_REWRITE),
287
0
    OPT_BIT(0, "updateref", &flags,
288
0
      N_("update the reference to the value of the top reflog entry"),
289
0
      EXPIRE_REFLOGS_UPDATE_REF),
290
0
    OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen")),
291
0
    OPT_CALLBACK_F(0, "expire", &cmd, N_("timestamp"),
292
0
             N_("prune entries older than the specified time"),
293
0
             PARSE_OPT_NONEG,
294
0
             expire_total_callback),
295
0
    OPT_CALLBACK_F(0, "expire-unreachable", &cmd, N_("timestamp"),
296
0
             N_("prune entries older than <time> that are not reachable from the current tip of the branch"),
297
0
             PARSE_OPT_NONEG,
298
0
             expire_unreachable_callback),
299
0
    OPT_BOOL(0, "stale-fix", &cmd.stalefix,
300
0
       N_("prune any reflog entries that point to broken commits")),
301
0
    OPT_BOOL(0, "all", &do_all, N_("process the reflogs of all references")),
302
0
    OPT_BOOL(0, "single-worktree", &single_worktree,
303
0
       N_("limits processing to reflogs from the current worktree only")),
304
0
    OPT_END()
305
0
  };
306
307
0
  default_reflog_expire_unreachable = now - 30 * 24 * 3600;
308
0
  default_reflog_expire = now - 90 * 24 * 3600;
309
0
  git_config(reflog_expire_config, NULL);
310
311
0
  save_commit_buffer = 0;
312
0
  do_all = status = 0;
313
314
0
  cmd.explicit_expiry = 0;
315
0
  cmd.expire_total = default_reflog_expire;
316
0
  cmd.expire_unreachable = default_reflog_expire_unreachable;
317
318
0
  argc = parse_options(argc, argv, prefix, options, reflog_expire_usage, 0);
319
320
0
  if (verbose)
321
0
    should_prune_fn = should_expire_reflog_ent_verbose;
322
323
  /*
324
   * We can trust the commits and objects reachable from refs
325
   * even in older repository.  We cannot trust what's reachable
326
   * from reflog if the repository was pruned with older git.
327
   */
328
0
  if (cmd.stalefix) {
329
0
    struct rev_info revs;
330
331
0
    repo_init_revisions(the_repository, &revs, prefix);
332
0
    revs.do_not_die_on_missing_objects = 1;
333
0
    revs.ignore_missing = 1;
334
0
    revs.ignore_missing_links = 1;
335
0
    if (verbose)
336
0
      printf(_("Marking reachable objects..."));
337
0
    mark_reachable_objects(&revs, 0, 0, NULL);
338
0
    release_revisions(&revs);
339
0
    if (verbose)
340
0
      putchar('\n');
341
0
  }
342
343
0
  if (do_all) {
344
0
    struct worktree_reflogs collected = {
345
0
      .reflogs = STRING_LIST_INIT_DUP,
346
0
    };
347
0
    struct string_list_item *item;
348
0
    struct worktree **worktrees, **p;
349
350
0
    worktrees = get_worktrees();
351
0
    for (p = worktrees; *p; p++) {
352
0
      if (single_worktree && !(*p)->is_current)
353
0
        continue;
354
0
      collected.worktree = *p;
355
0
      refs_for_each_reflog(get_worktree_ref_store(*p),
356
0
               collect_reflog, &collected);
357
0
    }
358
0
    free_worktrees(worktrees);
359
360
0
    for_each_string_list_item(item, &collected.reflogs) {
361
0
      struct expire_reflog_policy_cb cb = {
362
0
        .cmd = cmd,
363
0
        .dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),
364
0
      };
365
366
0
      set_reflog_expiry_param(&cb.cmd,  item->string);
367
0
      status |= refs_reflog_expire(get_main_ref_store(the_repository),
368
0
                 item->string, flags,
369
0
                 reflog_expiry_prepare,
370
0
                 should_prune_fn,
371
0
                 reflog_expiry_cleanup,
372
0
                 &cb);
373
0
    }
374
0
    string_list_clear(&collected.reflogs, 0);
375
0
  }
376
377
0
  for (i = 0; i < argc; i++) {
378
0
    char *ref;
379
0
    struct expire_reflog_policy_cb cb = { .cmd = cmd };
380
381
0
    if (!repo_dwim_log(the_repository, argv[i], strlen(argv[i]), NULL, &ref)) {
382
0
      status |= error(_("%s points nowhere!"), argv[i]);
383
0
      continue;
384
0
    }
385
0
    set_reflog_expiry_param(&cb.cmd, ref);
386
0
    status |= refs_reflog_expire(get_main_ref_store(the_repository),
387
0
               ref, flags,
388
0
               reflog_expiry_prepare,
389
0
               should_prune_fn,
390
0
               reflog_expiry_cleanup,
391
0
               &cb);
392
0
    free(ref);
393
0
  }
394
0
  return status;
395
0
}
396
397
static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
398
0
{
399
0
  int i, status = 0;
400
0
  unsigned int flags = 0;
401
0
  int verbose = 0;
402
403
0
  const struct option options[] = {
404
0
    OPT_BIT('n', "dry-run", &flags, N_("do not actually prune any entries"),
405
0
      EXPIRE_REFLOGS_DRY_RUN),
406
0
    OPT_BIT(0, "rewrite", &flags,
407
0
      N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"),
408
0
      EXPIRE_REFLOGS_REWRITE),
409
0
    OPT_BIT(0, "updateref", &flags,
410
0
      N_("update the reference to the value of the top reflog entry"),
411
0
      EXPIRE_REFLOGS_UPDATE_REF),
412
0
    OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen")),
413
0
    OPT_END()
414
0
  };
415
416
0
  argc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0);
417
418
0
  if (argc < 1)
419
0
    return error(_("no reflog specified to delete"));
420
421
0
  for (i = 0; i < argc; i++)
422
0
    status |= reflog_delete(argv[i], flags, verbose);
423
424
0
  return status;
425
0
}
426
427
static int cmd_reflog_exists(int argc, const char **argv, const char *prefix)
428
0
{
429
0
  struct option options[] = {
430
0
    OPT_END()
431
0
  };
432
0
  const char *refname;
433
434
0
  argc = parse_options(argc, argv, prefix, options, reflog_exists_usage,
435
0
           0);
436
0
  if (!argc)
437
0
    usage_with_options(reflog_exists_usage, options);
438
439
0
  refname = argv[0];
440
0
  if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
441
0
    die(_("invalid ref format: %s"), refname);
442
0
  return !refs_reflog_exists(get_main_ref_store(the_repository),
443
0
           refname);
444
0
}
445
446
/*
447
 * main "reflog"
448
 */
449
450
int cmd_reflog(int argc, const char **argv, const char *prefix)
451
0
{
452
0
  parse_opt_subcommand_fn *fn = NULL;
453
0
  struct option options[] = {
454
0
    OPT_SUBCOMMAND("show", &fn, cmd_reflog_show),
455
0
    OPT_SUBCOMMAND("list", &fn, cmd_reflog_list),
456
0
    OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire),
457
0
    OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete),
458
0
    OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists),
459
0
    OPT_END()
460
0
  };
461
462
0
  argc = parse_options(argc, argv, prefix, options, reflog_usage,
463
0
           PARSE_OPT_SUBCOMMAND_OPTIONAL |
464
0
           PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 |
465
0
           PARSE_OPT_KEEP_UNKNOWN_OPT);
466
0
  if (fn)
467
0
    return fn(argc - 1, argv + 1, prefix);
468
0
  else
469
0
    return cmd_log_reflog(argc, argv, prefix);
470
0
}