reflog: libify delete reflog function and helpers
[git/debian.git] / builtin / reflog.c
blobce1c45b7b60cbd8c59877d6a2e5cde4fbe53a3c3
1 #include "builtin.h"
2 #include "config.h"
3 #include "revision.h"
4 #include "reachable.h"
5 #include "worktree.h"
6 #include "reflog.h"
8 static const char reflog_exists_usage[] =
9 N_("git reflog exists <ref>");
11 static timestamp_t default_reflog_expire;
12 static timestamp_t default_reflog_expire_unreachable;
14 struct worktree_reflogs {
15 struct worktree *worktree;
16 struct string_list reflogs;
19 static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data)
21 struct worktree_reflogs *cb = cb_data;
22 struct worktree *worktree = cb->worktree;
23 struct strbuf newref = STRBUF_INIT;
26 * Avoid collecting the same shared ref multiple times because
27 * they are available via all worktrees.
29 if (!worktree->is_current && ref_type(ref) == REF_TYPE_NORMAL)
30 return 0;
32 strbuf_worktree_ref(worktree, &newref, ref);
33 string_list_append_nodup(&cb->reflogs, strbuf_detach(&newref, NULL));
35 return 0;
38 static struct reflog_expire_cfg {
39 struct reflog_expire_cfg *next;
40 timestamp_t expire_total;
41 timestamp_t expire_unreachable;
42 char pattern[FLEX_ARRAY];
43 } *reflog_expire_cfg, **reflog_expire_cfg_tail;
45 static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
47 struct reflog_expire_cfg *ent;
49 if (!reflog_expire_cfg_tail)
50 reflog_expire_cfg_tail = &reflog_expire_cfg;
52 for (ent = reflog_expire_cfg; ent; ent = ent->next)
53 if (!strncmp(ent->pattern, pattern, len) &&
54 ent->pattern[len] == '\0')
55 return ent;
57 FLEX_ALLOC_MEM(ent, pattern, pattern, len);
58 *reflog_expire_cfg_tail = ent;
59 reflog_expire_cfg_tail = &(ent->next);
60 return ent;
63 /* expiry timer slot */
64 #define EXPIRE_TOTAL 01
65 #define EXPIRE_UNREACH 02
67 static int reflog_expire_config(const char *var, const char *value, void *cb)
69 const char *pattern, *key;
70 size_t pattern_len;
71 timestamp_t expire;
72 int slot;
73 struct reflog_expire_cfg *ent;
75 if (parse_config_key(var, "gc", &pattern, &pattern_len, &key) < 0)
76 return git_default_config(var, value, cb);
78 if (!strcmp(key, "reflogexpire")) {
79 slot = EXPIRE_TOTAL;
80 if (git_config_expiry_date(&expire, var, value))
81 return -1;
82 } else if (!strcmp(key, "reflogexpireunreachable")) {
83 slot = EXPIRE_UNREACH;
84 if (git_config_expiry_date(&expire, var, value))
85 return -1;
86 } else
87 return git_default_config(var, value, cb);
89 if (!pattern) {
90 switch (slot) {
91 case EXPIRE_TOTAL:
92 default_reflog_expire = expire;
93 break;
94 case EXPIRE_UNREACH:
95 default_reflog_expire_unreachable = expire;
96 break;
98 return 0;
101 ent = find_cfg_ent(pattern, pattern_len);
102 if (!ent)
103 return -1;
104 switch (slot) {
105 case EXPIRE_TOTAL:
106 ent->expire_total = expire;
107 break;
108 case EXPIRE_UNREACH:
109 ent->expire_unreachable = expire;
110 break;
112 return 0;
115 static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, const char *ref)
117 struct reflog_expire_cfg *ent;
119 if (cb->explicit_expiry == (EXPIRE_TOTAL|EXPIRE_UNREACH))
120 return; /* both given explicitly -- nothing to tweak */
122 for (ent = reflog_expire_cfg; ent; ent = ent->next) {
123 if (!wildmatch(ent->pattern, ref, 0)) {
124 if (!(cb->explicit_expiry & EXPIRE_TOTAL))
125 cb->expire_total = ent->expire_total;
126 if (!(cb->explicit_expiry & EXPIRE_UNREACH))
127 cb->expire_unreachable = ent->expire_unreachable;
128 return;
133 * If unconfigured, make stash never expire
135 if (!strcmp(ref, "refs/stash")) {
136 if (!(cb->explicit_expiry & EXPIRE_TOTAL))
137 cb->expire_total = 0;
138 if (!(cb->explicit_expiry & EXPIRE_UNREACH))
139 cb->expire_unreachable = 0;
140 return;
143 /* Nothing matched -- use the default value */
144 if (!(cb->explicit_expiry & EXPIRE_TOTAL))
145 cb->expire_total = default_reflog_expire;
146 if (!(cb->explicit_expiry & EXPIRE_UNREACH))
147 cb->expire_unreachable = default_reflog_expire_unreachable;
150 static const char * reflog_expire_usage[] = {
151 N_("git reflog expire [--expire=<time>] "
152 "[--expire-unreachable=<time>] "
153 "[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] "
154 "[--verbose] [--all] <refs>..."),
155 NULL
158 static int expire_unreachable_callback(const struct option *opt,
159 const char *arg,
160 int unset)
162 struct cmd_reflog_expire_cb *cmd = opt->value;
164 if (parse_expiry_date(arg, &cmd->expire_unreachable))
165 die(_("invalid timestamp '%s' given to '--%s'"),
166 arg, opt->long_name);
168 cmd->explicit_expiry |= EXPIRE_UNREACH;
169 return 0;
172 static int expire_total_callback(const struct option *opt,
173 const char *arg,
174 int unset)
176 struct cmd_reflog_expire_cb *cmd = opt->value;
178 if (parse_expiry_date(arg, &cmd->expire_total))
179 die(_("invalid timestamp '%s' given to '--%s'"),
180 arg, opt->long_name);
182 cmd->explicit_expiry |= EXPIRE_TOTAL;
183 return 0;
186 static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
188 struct cmd_reflog_expire_cb cmd = { 0 };
189 timestamp_t now = time(NULL);
190 int i, status, do_all, all_worktrees = 1;
191 unsigned int flags = 0;
192 int verbose = 0;
193 reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;
194 const struct option options[] = {
195 OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"),
196 EXPIRE_REFLOGS_DRY_RUN),
197 OPT_BIT(0, "rewrite", &flags,
198 N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"),
199 EXPIRE_REFLOGS_REWRITE),
200 OPT_BIT(0, "updateref", &flags,
201 N_("update the reference to the value of the top reflog entry"),
202 EXPIRE_REFLOGS_UPDATE_REF),
203 OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen.")),
204 OPT_CALLBACK_F(0, "expire", &cmd, N_("timestamp"),
205 N_("prune entries older than the specified time"),
206 PARSE_OPT_NONEG,
207 expire_total_callback),
208 OPT_CALLBACK_F(0, "expire-unreachable", &cmd, N_("timestamp"),
209 N_("prune entries older than <time> that are not reachable from the current tip of the branch"),
210 PARSE_OPT_NONEG,
211 expire_unreachable_callback),
212 OPT_BOOL(0, "stale-fix", &cmd.stalefix,
213 N_("prune any reflog entries that point to broken commits")),
214 OPT_BOOL(0, "all", &do_all, N_("process the reflogs of all references")),
215 OPT_BOOL(1, "single-worktree", &all_worktrees,
216 N_("limits processing to reflogs from the current worktree only.")),
217 OPT_END()
220 default_reflog_expire_unreachable = now - 30 * 24 * 3600;
221 default_reflog_expire = now - 90 * 24 * 3600;
222 git_config(reflog_expire_config, NULL);
224 save_commit_buffer = 0;
225 do_all = status = 0;
227 cmd.explicit_expiry = 0;
228 cmd.expire_total = default_reflog_expire;
229 cmd.expire_unreachable = default_reflog_expire_unreachable;
231 argc = parse_options(argc, argv, prefix, options, reflog_expire_usage, 0);
233 if (verbose)
234 should_prune_fn = should_expire_reflog_ent_verbose;
237 * We can trust the commits and objects reachable from refs
238 * even in older repository. We cannot trust what's reachable
239 * from reflog if the repository was pruned with older git.
241 if (cmd.stalefix) {
242 struct rev_info revs;
244 repo_init_revisions(the_repository, &revs, prefix);
245 revs.do_not_die_on_missing_tree = 1;
246 revs.ignore_missing = 1;
247 revs.ignore_missing_links = 1;
248 if (verbose)
249 printf(_("Marking reachable objects..."));
250 mark_reachable_objects(&revs, 0, 0, NULL);
251 if (verbose)
252 putchar('\n');
255 if (do_all) {
256 struct worktree_reflogs collected = {
257 .reflogs = STRING_LIST_INIT_DUP,
259 struct string_list_item *item;
260 struct worktree **worktrees, **p;
262 worktrees = get_worktrees();
263 for (p = worktrees; *p; p++) {
264 if (!all_worktrees && !(*p)->is_current)
265 continue;
266 collected.worktree = *p;
267 refs_for_each_reflog(get_worktree_ref_store(*p),
268 collect_reflog, &collected);
270 free_worktrees(worktrees);
272 for_each_string_list_item(item, &collected.reflogs) {
273 struct expire_reflog_policy_cb cb = {
274 .cmd = cmd,
275 .dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),
278 set_reflog_expiry_param(&cb.cmd, item->string);
279 status |= reflog_expire(item->string, flags,
280 reflog_expiry_prepare,
281 should_prune_fn,
282 reflog_expiry_cleanup,
283 &cb);
285 string_list_clear(&collected.reflogs, 0);
288 for (i = 0; i < argc; i++) {
289 char *ref;
290 struct expire_reflog_policy_cb cb = { .cmd = cmd };
292 if (!dwim_log(argv[i], strlen(argv[i]), NULL, &ref)) {
293 status |= error(_("%s points nowhere!"), argv[i]);
294 continue;
296 set_reflog_expiry_param(&cb.cmd, ref);
297 status |= reflog_expire(ref, flags,
298 reflog_expiry_prepare,
299 should_prune_fn,
300 reflog_expiry_cleanup,
301 &cb);
302 free(ref);
304 return status;
307 static const char * reflog_delete_usage[] = {
308 N_("git reflog delete [--rewrite] [--updateref] "
309 "[--dry-run | -n] [--verbose] <refs>..."),
310 NULL
313 static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
315 int i, status = 0;
316 unsigned int flags = 0;
317 int verbose = 0;
319 const struct option options[] = {
320 OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"),
321 EXPIRE_REFLOGS_DRY_RUN),
322 OPT_BIT(0, "rewrite", &flags,
323 N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"),
324 EXPIRE_REFLOGS_REWRITE),
325 OPT_BIT(0, "updateref", &flags,
326 N_("update the reference to the value of the top reflog entry"),
327 EXPIRE_REFLOGS_UPDATE_REF),
328 OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen.")),
329 OPT_END()
332 argc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0);
334 if (argc < 1)
335 return error(_("no reflog specified to delete"));
337 for (i = 0; i < argc; i++)
338 status |= reflog_delete(argv[i], flags, verbose);
340 return status;
343 static int cmd_reflog_exists(int argc, const char **argv, const char *prefix)
345 int i, start = 0;
347 for (i = 1; i < argc; i++) {
348 const char *arg = argv[i];
349 if (!strcmp(arg, "--")) {
350 i++;
351 break;
353 else if (arg[0] == '-')
354 usage(_(reflog_exists_usage));
355 else
356 break;
359 start = i;
361 if (argc - start != 1)
362 usage(_(reflog_exists_usage));
364 if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL))
365 die(_("invalid ref format: %s"), argv[start]);
366 return !reflog_exists(argv[start]);
370 * main "reflog"
373 static const char reflog_usage[] =
374 "git reflog [ show | expire | delete | exists ]";
376 int cmd_reflog(int argc, const char **argv, const char *prefix)
378 if (argc > 1 && !strcmp(argv[1], "-h"))
379 usage(_(reflog_usage));
381 /* With no command, we default to showing it. */
382 if (argc < 2 || *argv[1] == '-')
383 return cmd_log_reflog(argc, argv, prefix);
385 if (!strcmp(argv[1], "show"))
386 return cmd_log_reflog(argc - 1, argv + 1, prefix);
388 if (!strcmp(argv[1], "expire"))
389 return cmd_reflog_expire(argc - 1, argv + 1, prefix);
391 if (!strcmp(argv[1], "delete"))
392 return cmd_reflog_delete(argc - 1, argv + 1, prefix);
394 if (!strcmp(argv[1], "exists"))
395 return cmd_reflog_exists(argc - 1, argv + 1, prefix);
397 return cmd_log_reflog(argc, argv, prefix);