expire_reflog(): move updateref to flags argument
[git/mjg.git] / builtin / reflog.c
blobe238fe036c46c2c5ef24863bad824a787eaf249a
1 #include "builtin.h"
2 #include "lockfile.h"
3 #include "commit.h"
4 #include "refs.h"
5 #include "dir.h"
6 #include "tree-walk.h"
7 #include "diff.h"
8 #include "revision.h"
9 #include "reachable.h"
12 * reflog expire
15 static const char reflog_expire_usage[] =
16 "git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
17 static const char reflog_delete_usage[] =
18 "git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
20 static unsigned long default_reflog_expire;
21 static unsigned long default_reflog_expire_unreachable;
23 enum expire_reflog_flags {
24 EXPIRE_REFLOGS_DRY_RUN = 1 << 0,
25 EXPIRE_REFLOGS_UPDATE_REF = 1 << 1
28 struct cmd_reflog_expire_cb {
29 struct rev_info revs;
30 int stalefix;
31 int rewrite;
32 int verbose;
33 unsigned long expire_total;
34 unsigned long expire_unreachable;
35 int recno;
38 struct expire_reflog_cb {
39 FILE *newlog;
40 enum {
41 UE_NORMAL,
42 UE_ALWAYS,
43 UE_HEAD
44 } unreachable_expire_kind;
45 struct commit_list *mark_list;
46 unsigned long mark_limit;
47 struct cmd_reflog_expire_cb *cmd;
48 unsigned char last_kept_sha1[20];
49 struct commit *tip_commit;
50 struct commit_list *tips;
53 struct collected_reflog {
54 unsigned char sha1[20];
55 char reflog[FLEX_ARRAY];
57 struct collect_reflog_cb {
58 struct collected_reflog **e;
59 int alloc;
60 int nr;
63 #define INCOMPLETE (1u<<10)
64 #define STUDYING (1u<<11)
65 #define REACHABLE (1u<<12)
67 static int tree_is_complete(const unsigned char *sha1)
69 struct tree_desc desc;
70 struct name_entry entry;
71 int complete;
72 struct tree *tree;
74 tree = lookup_tree(sha1);
75 if (!tree)
76 return 0;
77 if (tree->object.flags & SEEN)
78 return 1;
79 if (tree->object.flags & INCOMPLETE)
80 return 0;
82 if (!tree->buffer) {
83 enum object_type type;
84 unsigned long size;
85 void *data = read_sha1_file(sha1, &type, &size);
86 if (!data) {
87 tree->object.flags |= INCOMPLETE;
88 return 0;
90 tree->buffer = data;
91 tree->size = size;
93 init_tree_desc(&desc, tree->buffer, tree->size);
94 complete = 1;
95 while (tree_entry(&desc, &entry)) {
96 if (!has_sha1_file(entry.sha1) ||
97 (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) {
98 tree->object.flags |= INCOMPLETE;
99 complete = 0;
102 free_tree_buffer(tree);
104 if (complete)
105 tree->object.flags |= SEEN;
106 return complete;
109 static int commit_is_complete(struct commit *commit)
111 struct object_array study;
112 struct object_array found;
113 int is_incomplete = 0;
114 int i;
116 /* early return */
117 if (commit->object.flags & SEEN)
118 return 1;
119 if (commit->object.flags & INCOMPLETE)
120 return 0;
122 * Find all commits that are reachable and are not marked as
123 * SEEN. Then make sure the trees and blobs contained are
124 * complete. After that, mark these commits also as SEEN.
125 * If some of the objects that are needed to complete this
126 * commit are missing, mark this commit as INCOMPLETE.
128 memset(&study, 0, sizeof(study));
129 memset(&found, 0, sizeof(found));
130 add_object_array(&commit->object, NULL, &study);
131 add_object_array(&commit->object, NULL, &found);
132 commit->object.flags |= STUDYING;
133 while (study.nr) {
134 struct commit *c;
135 struct commit_list *parent;
137 c = (struct commit *)study.objects[--study.nr].item;
138 if (!c->object.parsed && !parse_object(c->object.sha1))
139 c->object.flags |= INCOMPLETE;
141 if (c->object.flags & INCOMPLETE) {
142 is_incomplete = 1;
143 break;
145 else if (c->object.flags & SEEN)
146 continue;
147 for (parent = c->parents; parent; parent = parent->next) {
148 struct commit *p = parent->item;
149 if (p->object.flags & STUDYING)
150 continue;
151 p->object.flags |= STUDYING;
152 add_object_array(&p->object, NULL, &study);
153 add_object_array(&p->object, NULL, &found);
156 if (!is_incomplete) {
158 * make sure all commits in "found" array have all the
159 * necessary objects.
161 for (i = 0; i < found.nr; i++) {
162 struct commit *c =
163 (struct commit *)found.objects[i].item;
164 if (!tree_is_complete(c->tree->object.sha1)) {
165 is_incomplete = 1;
166 c->object.flags |= INCOMPLETE;
169 if (!is_incomplete) {
170 /* mark all found commits as complete, iow SEEN */
171 for (i = 0; i < found.nr; i++)
172 found.objects[i].item->flags |= SEEN;
175 /* clear flags from the objects we traversed */
176 for (i = 0; i < found.nr; i++)
177 found.objects[i].item->flags &= ~STUDYING;
178 if (is_incomplete)
179 commit->object.flags |= INCOMPLETE;
180 else {
182 * If we come here, we have (1) traversed the ancestry chain
183 * from the "commit" until we reach SEEN commits (which are
184 * known to be complete), and (2) made sure that the commits
185 * encountered during the above traversal refer to trees that
186 * are complete. Which means that we know *all* the commits
187 * we have seen during this process are complete.
189 for (i = 0; i < found.nr; i++)
190 found.objects[i].item->flags |= SEEN;
192 /* free object arrays */
193 free(study.objects);
194 free(found.objects);
195 return !is_incomplete;
198 static int keep_entry(struct commit **it, unsigned char *sha1)
200 struct commit *commit;
202 if (is_null_sha1(sha1))
203 return 1;
204 commit = lookup_commit_reference_gently(sha1, 1);
205 if (!commit)
206 return 0;
209 * Make sure everything in this commit exists.
211 * We have walked all the objects reachable from the refs
212 * and cache earlier. The commits reachable by this commit
213 * must meet SEEN commits -- and then we should mark them as
214 * SEEN as well.
216 if (!commit_is_complete(commit))
217 return 0;
218 *it = commit;
219 return 1;
223 * Starting from commits in the cb->mark_list, mark commits that are
224 * reachable from them. Stop the traversal at commits older than
225 * the expire_limit and queue them back, so that the caller can call
226 * us again to restart the traversal with longer expire_limit.
228 static void mark_reachable(struct expire_reflog_cb *cb)
230 struct commit *commit;
231 struct commit_list *pending;
232 unsigned long expire_limit = cb->mark_limit;
233 struct commit_list *leftover = NULL;
235 for (pending = cb->mark_list; pending; pending = pending->next)
236 pending->item->object.flags &= ~REACHABLE;
238 pending = cb->mark_list;
239 while (pending) {
240 struct commit_list *entry = pending;
241 struct commit_list *parent;
242 pending = entry->next;
243 commit = entry->item;
244 free(entry);
245 if (commit->object.flags & REACHABLE)
246 continue;
247 if (parse_commit(commit))
248 continue;
249 commit->object.flags |= REACHABLE;
250 if (commit->date < expire_limit) {
251 commit_list_insert(commit, &leftover);
252 continue;
254 commit->object.flags |= REACHABLE;
255 parent = commit->parents;
256 while (parent) {
257 commit = parent->item;
258 parent = parent->next;
259 if (commit->object.flags & REACHABLE)
260 continue;
261 commit_list_insert(commit, &pending);
264 cb->mark_list = leftover;
267 static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
270 * We may or may not have the commit yet - if not, look it
271 * up using the supplied sha1.
273 if (!commit) {
274 if (is_null_sha1(sha1))
275 return 0;
277 commit = lookup_commit_reference_gently(sha1, 1);
279 /* Not a commit -- keep it */
280 if (!commit)
281 return 0;
284 /* Reachable from the current ref? Don't prune. */
285 if (commit->object.flags & REACHABLE)
286 return 0;
288 if (cb->mark_list && cb->mark_limit) {
289 cb->mark_limit = 0; /* dig down to the root */
290 mark_reachable(cb);
293 return !(commit->object.flags & REACHABLE);
297 * Return true iff the specified reflog entry should be expired.
299 static int should_expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
300 const char *email, unsigned long timestamp, int tz,
301 const char *message, void *cb_data)
303 struct expire_reflog_cb *cb = cb_data;
304 struct commit *old, *new;
306 if (timestamp < cb->cmd->expire_total)
307 return 1;
309 old = new = NULL;
310 if (cb->cmd->stalefix &&
311 (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
312 return 1;
314 if (timestamp < cb->cmd->expire_unreachable) {
315 if (cb->unreachable_expire_kind == UE_ALWAYS)
316 return 1;
317 if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1))
318 return 1;
321 if (cb->cmd->recno && --(cb->cmd->recno) == 0)
322 return 1;
324 return 0;
327 static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
328 const char *email, unsigned long timestamp, int tz,
329 const char *message, void *cb_data)
331 struct expire_reflog_cb *cb = cb_data;
333 if (cb->cmd->rewrite)
334 osha1 = cb->last_kept_sha1;
336 if (should_expire_reflog_ent(osha1, nsha1, email, timestamp, tz,
337 message, cb_data)) {
338 if (!cb->newlog)
339 printf("would prune %s", message);
340 else if (cb->cmd->verbose)
341 printf("prune %s", message);
342 } else {
343 if (cb->newlog) {
344 char sign = (tz < 0) ? '-' : '+';
345 int zone = (tz < 0) ? (-tz) : tz;
346 fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s",
347 sha1_to_hex(osha1), sha1_to_hex(nsha1),
348 email, timestamp, sign, zone,
349 message);
350 hashcpy(cb->last_kept_sha1, nsha1);
352 if (cb->cmd->verbose)
353 printf("keep %s", message);
355 return 0;
358 static int push_tip_to_list(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
360 struct commit_list **list = cb_data;
361 struct commit *tip_commit;
362 if (flags & REF_ISSYMREF)
363 return 0;
364 tip_commit = lookup_commit_reference_gently(sha1, 1);
365 if (!tip_commit)
366 return 0;
367 commit_list_insert(tip_commit, list);
368 return 0;
371 static void reflog_expiry_prepare(const char *refname,
372 const unsigned char *sha1,
373 struct expire_reflog_cb *cb)
375 if (!cb->cmd->expire_unreachable || !strcmp(refname, "HEAD")) {
376 cb->tip_commit = NULL;
377 cb->unreachable_expire_kind = UE_HEAD;
378 } else {
379 cb->tip_commit = lookup_commit_reference_gently(sha1, 1);
380 if (!cb->tip_commit)
381 cb->unreachable_expire_kind = UE_ALWAYS;
382 else
383 cb->unreachable_expire_kind = UE_NORMAL;
386 if (cb->cmd->expire_unreachable <= cb->cmd->expire_total)
387 cb->unreachable_expire_kind = UE_ALWAYS;
389 cb->mark_list = NULL;
390 cb->tips = NULL;
391 if (cb->unreachable_expire_kind != UE_ALWAYS) {
392 if (cb->unreachable_expire_kind == UE_HEAD) {
393 struct commit_list *elem;
394 for_each_ref(push_tip_to_list, &cb->tips);
395 for (elem = cb->tips; elem; elem = elem->next)
396 commit_list_insert(elem->item, &cb->mark_list);
397 } else {
398 commit_list_insert(cb->tip_commit, &cb->mark_list);
400 cb->mark_limit = cb->cmd->expire_total;
401 mark_reachable(cb);
405 static void reflog_expiry_cleanup(struct expire_reflog_cb *cb)
407 if (cb->unreachable_expire_kind != UE_ALWAYS) {
408 if (cb->unreachable_expire_kind == UE_HEAD) {
409 struct commit_list *elem;
410 for (elem = cb->tips; elem; elem = elem->next)
411 clear_commit_marks(elem->item, REACHABLE);
412 free_commit_list(cb->tips);
413 } else {
414 clear_commit_marks(cb->tip_commit, REACHABLE);
419 static int expire_reflog(const char *refname, const unsigned char *sha1,
420 unsigned int flags, struct cmd_reflog_expire_cb *cmd)
422 static struct lock_file reflog_lock;
423 struct expire_reflog_cb cb;
424 struct ref_lock *lock;
425 char *log_file;
426 int status = 0;
428 memset(&cb, 0, sizeof(cb));
431 * The reflog file is locked by holding the lock on the
432 * reference itself, plus we might need to update the
433 * reference if --updateref was specified:
435 lock = lock_any_ref_for_update(refname, sha1, 0, NULL);
436 if (!lock)
437 return error("cannot lock ref '%s'", refname);
438 if (!reflog_exists(refname)) {
439 unlock_ref(lock);
440 return 0;
443 log_file = git_pathdup("logs/%s", refname);
444 if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) {
446 * Even though holding $GIT_DIR/logs/$reflog.lock has
447 * no locking implications, we use the lock_file
448 * machinery here anyway because it does a lot of the
449 * work we need, including cleaning up if the program
450 * exits unexpectedly.
452 if (hold_lock_file_for_update(&reflog_lock, log_file, 0) < 0) {
453 struct strbuf err = STRBUF_INIT;
454 unable_to_lock_message(log_file, errno, &err);
455 error("%s", err.buf);
456 strbuf_release(&err);
457 goto failure;
459 cb.newlog = fdopen_lock_file(&reflog_lock, "w");
460 if (!cb.newlog) {
461 error("cannot fdopen %s (%s)",
462 reflog_lock.filename.buf, strerror(errno));
463 goto failure;
467 cb.cmd = cmd;
469 reflog_expiry_prepare(refname, sha1, &cb);
470 for_each_reflog_ent(refname, expire_reflog_ent, &cb);
471 reflog_expiry_cleanup(&cb);
473 if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) {
474 if (close_lock_file(&reflog_lock)) {
475 status |= error("couldn't write %s: %s", log_file,
476 strerror(errno));
477 } else if ((flags & EXPIRE_REFLOGS_UPDATE_REF) &&
478 (write_in_full(lock->lock_fd,
479 sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
480 write_str_in_full(lock->lock_fd, "\n") != 1 ||
481 close_ref(lock) < 0)) {
482 status |= error("couldn't write %s",
483 lock->lk->filename.buf);
484 rollback_lock_file(&reflog_lock);
485 } else if (commit_lock_file(&reflog_lock)) {
486 status |= error("unable to commit reflog '%s' (%s)",
487 log_file, strerror(errno));
488 } else if ((flags & EXPIRE_REFLOGS_UPDATE_REF) && commit_ref(lock)) {
489 status |= error("couldn't set %s", lock->ref_name);
492 free(log_file);
493 unlock_ref(lock);
494 return status;
496 failure:
497 rollback_lock_file(&reflog_lock);
498 free(log_file);
499 unlock_ref(lock);
500 return -1;
503 static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
505 struct collected_reflog *e;
506 struct collect_reflog_cb *cb = cb_data;
507 size_t namelen = strlen(ref);
509 e = xmalloc(sizeof(*e) + namelen + 1);
510 hashcpy(e->sha1, sha1);
511 memcpy(e->reflog, ref, namelen + 1);
512 ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
513 cb->e[cb->nr++] = e;
514 return 0;
517 static struct reflog_expire_cfg {
518 struct reflog_expire_cfg *next;
519 unsigned long expire_total;
520 unsigned long expire_unreachable;
521 size_t len;
522 char pattern[FLEX_ARRAY];
523 } *reflog_expire_cfg, **reflog_expire_cfg_tail;
525 static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
527 struct reflog_expire_cfg *ent;
529 if (!reflog_expire_cfg_tail)
530 reflog_expire_cfg_tail = &reflog_expire_cfg;
532 for (ent = reflog_expire_cfg; ent; ent = ent->next)
533 if (ent->len == len &&
534 !memcmp(ent->pattern, pattern, len))
535 return ent;
537 ent = xcalloc(1, (sizeof(*ent) + len));
538 memcpy(ent->pattern, pattern, len);
539 ent->len = len;
540 *reflog_expire_cfg_tail = ent;
541 reflog_expire_cfg_tail = &(ent->next);
542 return ent;
545 static int parse_expire_cfg_value(const char *var, const char *value, unsigned long *expire)
547 if (!value)
548 return config_error_nonbool(var);
549 if (parse_expiry_date(value, expire))
550 return error(_("%s' for '%s' is not a valid timestamp"),
551 value, var);
552 return 0;
555 /* expiry timer slot */
556 #define EXPIRE_TOTAL 01
557 #define EXPIRE_UNREACH 02
559 static int reflog_expire_config(const char *var, const char *value, void *cb)
561 const char *pattern, *key;
562 int pattern_len;
563 unsigned long expire;
564 int slot;
565 struct reflog_expire_cfg *ent;
567 if (parse_config_key(var, "gc", &pattern, &pattern_len, &key) < 0)
568 return git_default_config(var, value, cb);
570 if (!strcmp(key, "reflogexpire")) {
571 slot = EXPIRE_TOTAL;
572 if (parse_expire_cfg_value(var, value, &expire))
573 return -1;
574 } else if (!strcmp(key, "reflogexpireunreachable")) {
575 slot = EXPIRE_UNREACH;
576 if (parse_expire_cfg_value(var, value, &expire))
577 return -1;
578 } else
579 return git_default_config(var, value, cb);
581 if (!pattern) {
582 switch (slot) {
583 case EXPIRE_TOTAL:
584 default_reflog_expire = expire;
585 break;
586 case EXPIRE_UNREACH:
587 default_reflog_expire_unreachable = expire;
588 break;
590 return 0;
593 ent = find_cfg_ent(pattern, pattern_len);
594 if (!ent)
595 return -1;
596 switch (slot) {
597 case EXPIRE_TOTAL:
598 ent->expire_total = expire;
599 break;
600 case EXPIRE_UNREACH:
601 ent->expire_unreachable = expire;
602 break;
604 return 0;
607 static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref)
609 struct reflog_expire_cfg *ent;
611 if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH))
612 return; /* both given explicitly -- nothing to tweak */
614 for (ent = reflog_expire_cfg; ent; ent = ent->next) {
615 if (!wildmatch(ent->pattern, ref, 0, NULL)) {
616 if (!(slot & EXPIRE_TOTAL))
617 cb->expire_total = ent->expire_total;
618 if (!(slot & EXPIRE_UNREACH))
619 cb->expire_unreachable = ent->expire_unreachable;
620 return;
625 * If unconfigured, make stash never expire
627 if (!strcmp(ref, "refs/stash")) {
628 if (!(slot & EXPIRE_TOTAL))
629 cb->expire_total = 0;
630 if (!(slot & EXPIRE_UNREACH))
631 cb->expire_unreachable = 0;
632 return;
635 /* Nothing matched -- use the default value */
636 if (!(slot & EXPIRE_TOTAL))
637 cb->expire_total = default_reflog_expire;
638 if (!(slot & EXPIRE_UNREACH))
639 cb->expire_unreachable = default_reflog_expire_unreachable;
642 static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
644 struct cmd_reflog_expire_cb cb;
645 unsigned long now = time(NULL);
646 int i, status, do_all;
647 int explicit_expiry = 0;
648 unsigned int flags = 0;
650 default_reflog_expire_unreachable = now - 30 * 24 * 3600;
651 default_reflog_expire = now - 90 * 24 * 3600;
652 git_config(reflog_expire_config, NULL);
654 save_commit_buffer = 0;
655 do_all = status = 0;
656 memset(&cb, 0, sizeof(cb));
658 cb.expire_total = default_reflog_expire;
659 cb.expire_unreachable = default_reflog_expire_unreachable;
661 for (i = 1; i < argc; i++) {
662 const char *arg = argv[i];
663 if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
664 flags |= EXPIRE_REFLOGS_DRY_RUN;
665 else if (starts_with(arg, "--expire=")) {
666 if (parse_expiry_date(arg + 9, &cb.expire_total))
667 die(_("'%s' is not a valid timestamp"), arg);
668 explicit_expiry |= EXPIRE_TOTAL;
670 else if (starts_with(arg, "--expire-unreachable=")) {
671 if (parse_expiry_date(arg + 21, &cb.expire_unreachable))
672 die(_("'%s' is not a valid timestamp"), arg);
673 explicit_expiry |= EXPIRE_UNREACH;
675 else if (!strcmp(arg, "--stale-fix"))
676 cb.stalefix = 1;
677 else if (!strcmp(arg, "--rewrite"))
678 cb.rewrite = 1;
679 else if (!strcmp(arg, "--updateref"))
680 flags |= EXPIRE_REFLOGS_UPDATE_REF;
681 else if (!strcmp(arg, "--all"))
682 do_all = 1;
683 else if (!strcmp(arg, "--verbose"))
684 cb.verbose = 1;
685 else if (!strcmp(arg, "--")) {
686 i++;
687 break;
689 else if (arg[0] == '-')
690 usage(reflog_expire_usage);
691 else
692 break;
696 * We can trust the commits and objects reachable from refs
697 * even in older repository. We cannot trust what's reachable
698 * from reflog if the repository was pruned with older git.
700 if (cb.stalefix) {
701 init_revisions(&cb.revs, prefix);
702 if (cb.verbose)
703 printf("Marking reachable objects...");
704 mark_reachable_objects(&cb.revs, 0, 0, NULL);
705 if (cb.verbose)
706 putchar('\n');
709 if (do_all) {
710 struct collect_reflog_cb collected;
711 int i;
713 memset(&collected, 0, sizeof(collected));
714 for_each_reflog(collect_reflog, &collected);
715 for (i = 0; i < collected.nr; i++) {
716 struct collected_reflog *e = collected.e[i];
717 set_reflog_expiry_param(&cb, explicit_expiry, e->reflog);
718 status |= expire_reflog(e->reflog, e->sha1, flags, &cb);
719 free(e);
721 free(collected.e);
724 for (; i < argc; i++) {
725 char *ref;
726 unsigned char sha1[20];
727 if (!dwim_log(argv[i], strlen(argv[i]), sha1, &ref)) {
728 status |= error("%s points nowhere!", argv[i]);
729 continue;
731 set_reflog_expiry_param(&cb, explicit_expiry, ref);
732 status |= expire_reflog(ref, sha1, flags, &cb);
734 return status;
737 static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
738 const char *email, unsigned long timestamp, int tz,
739 const char *message, void *cb_data)
741 struct cmd_reflog_expire_cb *cb = cb_data;
742 if (!cb->expire_total || timestamp < cb->expire_total)
743 cb->recno++;
744 return 0;
747 static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
749 struct cmd_reflog_expire_cb cb;
750 int i, status = 0;
751 unsigned int flags = 0;
753 memset(&cb, 0, sizeof(cb));
755 for (i = 1; i < argc; i++) {
756 const char *arg = argv[i];
757 if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
758 flags |= EXPIRE_REFLOGS_DRY_RUN;
759 else if (!strcmp(arg, "--rewrite"))
760 cb.rewrite = 1;
761 else if (!strcmp(arg, "--updateref"))
762 flags |= EXPIRE_REFLOGS_UPDATE_REF;
763 else if (!strcmp(arg, "--verbose"))
764 cb.verbose = 1;
765 else if (!strcmp(arg, "--")) {
766 i++;
767 break;
769 else if (arg[0] == '-')
770 usage(reflog_delete_usage);
771 else
772 break;
775 if (argc - i < 1)
776 return error("Nothing to delete?");
778 for ( ; i < argc; i++) {
779 const char *spec = strstr(argv[i], "@{");
780 unsigned char sha1[20];
781 char *ep, *ref;
782 int recno;
784 if (!spec) {
785 status |= error("Not a reflog: %s", argv[i]);
786 continue;
789 if (!dwim_log(argv[i], spec - argv[i], sha1, &ref)) {
790 status |= error("no reflog for '%s'", argv[i]);
791 continue;
794 recno = strtoul(spec + 2, &ep, 10);
795 if (*ep == '}') {
796 cb.recno = -recno;
797 for_each_reflog_ent(ref, count_reflog_ent, &cb);
798 } else {
799 cb.expire_total = approxidate(spec + 2);
800 for_each_reflog_ent(ref, count_reflog_ent, &cb);
801 cb.expire_total = 0;
804 status |= expire_reflog(ref, sha1, flags, &cb);
805 free(ref);
807 return status;
811 * main "reflog"
814 static const char reflog_usage[] =
815 "git reflog [ show | expire | delete ]";
817 int cmd_reflog(int argc, const char **argv, const char *prefix)
819 if (argc > 1 && !strcmp(argv[1], "-h"))
820 usage(reflog_usage);
822 /* With no command, we default to showing it. */
823 if (argc < 2 || *argv[1] == '-')
824 return cmd_log_reflog(argc, argv, prefix);
826 if (!strcmp(argv[1], "show"))
827 return cmd_log_reflog(argc - 1, argv + 1, prefix);
829 if (!strcmp(argv[1], "expire"))
830 return cmd_reflog_expire(argc - 1, argv + 1, prefix);
832 if (!strcmp(argv[1], "delete"))
833 return cmd_reflog_delete(argc - 1, argv + 1, prefix);
835 return cmd_log_reflog(argc, argv, prefix);