/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 | } |