reflog.c 18.8 KB
Newer Older
Junio C Hamano's avatar
Junio C Hamano committed
1
#include "builtin.h"
2
#include "config.h"
3
#include "lockfile.h"
4
#include "object-store.h"
5
#include "repository.h"
Junio C Hamano's avatar
Junio C Hamano committed
6 7 8
#include "commit.h"
#include "refs.h"
#include "dir.h"
9
#include "tree-walk.h"
10 11 12 13
#include "diff.h"
#include "revision.h"
#include "reachable.h"

14
/* NEEDSWORK: switch to using parse_options */
15
static const char reflog_expire_usage[] =
16
"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>...";
17
static const char reflog_delete_usage[] =
18
"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>...";
19 20
static const char reflog_exists_usage[] =
"git reflog exists <ref>";
Junio C Hamano's avatar
Junio C Hamano committed
21

22 23
static timestamp_t default_reflog_expire;
static timestamp_t default_reflog_expire_unreachable;
24

25 26 27
struct cmd_reflog_expire_cb {
	struct rev_info revs;
	int stalefix;
28 29
	timestamp_t expire_total;
	timestamp_t expire_unreachable;
30
	int recno;
31 32
};

33
struct expire_reflog_policy_cb {
34 35 36 37 38
	enum {
		UE_NORMAL,
		UE_ALWAYS,
		UE_HEAD
	} unreachable_expire_kind;
39 40
	struct commit_list *mark_list;
	unsigned long mark_limit;
41
	struct cmd_reflog_expire_cb cmd;
42 43
	struct commit *tip_commit;
	struct commit_list *tips;
Junio C Hamano's avatar
Junio C Hamano committed
44 45
};

46
struct collected_reflog {
47
	struct object_id oid;
48 49
	char reflog[FLEX_ARRAY];
};
50

51 52 53 54 55 56
struct collect_reflog_cb {
	struct collected_reflog **e;
	int alloc;
	int nr;
};

57
/* Remember to update object flag allocation in object.h */
58 59
#define INCOMPLETE	(1u<<10)
#define STUDYING	(1u<<11)
60
#define REACHABLE	(1u<<12)
61

62
static int tree_is_complete(const struct object_id *oid)
63 64
{
	struct tree_desc desc;
65 66 67
	struct name_entry entry;
	int complete;
	struct tree *tree;
68

69
	tree = lookup_tree(the_repository, oid);
70
	if (!tree)
71
		return 0;
72 73 74 75 76
	if (tree->object.flags & SEEN)
		return 1;
	if (tree->object.flags & INCOMPLETE)
		return 0;

77
	if (!tree->buffer) {
78
		enum object_type type;
79
		unsigned long size;
80
		void *data = read_object_file(oid, &type, &size);
81 82
		if (!data) {
			tree->object.flags |= INCOMPLETE;
83 84
			return 0;
		}
85
		tree->buffer = data;
86
		tree->size = size;
87
	}
88
	init_tree_desc(&desc, tree->buffer, tree->size);
89 90
	complete = 1;
	while (tree_entry(&desc, &entry)) {
91
		if (!has_sha1_file(entry.oid->hash) ||
92
		    (S_ISDIR(entry.mode) && !tree_is_complete(entry.oid))) {
93 94 95 96
			tree->object.flags |= INCOMPLETE;
			complete = 0;
		}
	}
97
	free_tree_buffer(tree);
98

99 100 101 102
	if (complete)
		tree->object.flags |= SEEN;
	return complete;
}
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131

static int commit_is_complete(struct commit *commit)
{
	struct object_array study;
	struct object_array found;
	int is_incomplete = 0;
	int i;

	/* early return */
	if (commit->object.flags & SEEN)
		return 1;
	if (commit->object.flags & INCOMPLETE)
		return 0;
	/*
	 * Find all commits that are reachable and are not marked as
	 * SEEN.  Then make sure the trees and blobs contained are
	 * complete.  After that, mark these commits also as SEEN.
	 * If some of the objects that are needed to complete this
	 * commit are missing, mark this commit as INCOMPLETE.
	 */
	memset(&study, 0, sizeof(study));
	memset(&found, 0, sizeof(found));
	add_object_array(&commit->object, NULL, &study);
	add_object_array(&commit->object, NULL, &found);
	commit->object.flags |= STUDYING;
	while (study.nr) {
		struct commit *c;
		struct commit_list *parent;

132
		c = (struct commit *)object_array_pop(&study);
133
		if (!c->object.parsed && !parse_object(the_repository, &c->object.oid))
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
			c->object.flags |= INCOMPLETE;

		if (c->object.flags & INCOMPLETE) {
			is_incomplete = 1;
			break;
		}
		else if (c->object.flags & SEEN)
			continue;
		for (parent = c->parents; parent; parent = parent->next) {
			struct commit *p = parent->item;
			if (p->object.flags & STUDYING)
				continue;
			p->object.flags |= STUDYING;
			add_object_array(&p->object, NULL, &study);
			add_object_array(&p->object, NULL, &found);
		}
	}
	if (!is_incomplete) {
152 153
		/*
		 * make sure all commits in "found" array have all the
154 155
		 * necessary objects.
		 */
156
		for (i = 0; i < found.nr; i++) {
157 158
			struct commit *c =
				(struct commit *)found.objects[i].item;
159
			if (!tree_is_complete(get_commit_tree_oid(c))) {
160
				is_incomplete = 1;
161 162
				c->object.flags |= INCOMPLETE;
			}
163 164 165 166 167 168 169 170 171 172 173 174
		}
		if (!is_incomplete) {
			/* mark all found commits as complete, iow SEEN */
			for (i = 0; i < found.nr; i++)
				found.objects[i].item->flags |= SEEN;
		}
	}
	/* clear flags from the objects we traversed */
	for (i = 0; i < found.nr; i++)
		found.objects[i].item->flags &= ~STUDYING;
	if (is_incomplete)
		commit->object.flags |= INCOMPLETE;
175 176 177 178 179 180 181 182 183 184 185 186
	else {
		/*
		 * If we come here, we have (1) traversed the ancestry chain
		 * from the "commit" until we reach SEEN commits (which are
		 * known to be complete), and (2) made sure that the commits
		 * encountered during the above traversal refer to trees that
		 * are complete.  Which means that we know *all* the commits
		 * we have seen during this process are complete.
		 */
		for (i = 0; i < found.nr; i++)
			found.objects[i].item->flags |= SEEN;
	}
187
	/* free object arrays */
188 189
	object_array_clear(&study);
	object_array_clear(&found);
190 191 192
	return !is_incomplete;
}

193
static int keep_entry(struct commit **it, struct object_id *oid)
Junio C Hamano's avatar
Junio C Hamano committed
194
{
195 196
	struct commit *commit;

197
	if (is_null_oid(oid))
Junio C Hamano's avatar
Junio C Hamano committed
198
		return 1;
199
	commit = lookup_commit_reference_gently(the_repository, oid, 1);
200 201 202
	if (!commit)
		return 0;

203 204 205 206 207 208 209 210 211
	/*
	 * Make sure everything in this commit exists.
	 *
	 * We have walked all the objects reachable from the refs
	 * and cache earlier.  The commits reachable by this commit
	 * must meet SEEN commits -- and then we should mark them as
	 * SEEN as well.
	 */
	if (!commit_is_complete(commit))
212 213 214
		return 0;
	*it = commit;
	return 1;
Junio C Hamano's avatar
Junio C Hamano committed
215 216
}

217 218 219 220 221 222
/*
 * Starting from commits in the cb->mark_list, mark commits that are
 * reachable from them.  Stop the traversal at commits older than
 * the expire_limit and queue them back, so that the caller can call
 * us again to restart the traversal with longer expire_limit.
 */
223
static void mark_reachable(struct expire_reflog_policy_cb *cb)
224
{
225
	struct commit_list *pending;
226
	timestamp_t expire_limit = cb->mark_limit;
227
	struct commit_list *leftover = NULL;
228

229 230
	for (pending = cb->mark_list; pending; pending = pending->next)
		pending->item->object.flags &= ~REACHABLE;
231

232
	pending = cb->mark_list;
233 234
	while (pending) {
		struct commit_list *parent;
235
		struct commit *commit = pop_commit(&pending);
236 237 238 239 240
		if (commit->object.flags & REACHABLE)
			continue;
		if (parse_commit(commit))
			continue;
		commit->object.flags |= REACHABLE;
241 242
		if (commit->date < expire_limit) {
			commit_list_insert(commit, &leftover);
243
			continue;
244 245
		}
		commit->object.flags |= REACHABLE;
246 247 248 249 250 251 252 253 254
		parent = commit->parents;
		while (parent) {
			commit = parent->item;
			parent = parent->next;
			if (commit->object.flags & REACHABLE)
				continue;
			commit_list_insert(commit, &pending);
		}
	}
255 256 257
	cb->mark_list = leftover;
}

258
static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)
259 260 261 262 263 264
{
	/*
	 * We may or may not have the commit yet - if not, look it
	 * up using the supplied sha1.
	 */
	if (!commit) {
265
		if (is_null_oid(oid))
266 267
			return 0;

268 269
		commit = lookup_commit_reference_gently(the_repository, oid,
							1);
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285

		/* Not a commit -- keep it */
		if (!commit)
			return 0;
	}

	/* Reachable from the current ref?  Don't prune. */
	if (commit->object.flags & REACHABLE)
		return 0;

	if (cb->mark_list && cb->mark_limit) {
		cb->mark_limit = 0; /* dig down to the root */
		mark_reachable(cb);
	}

	return !(commit->object.flags & REACHABLE);
286 287
}

288 289 290
/*
 * Return true iff the specified reflog entry should be expired.
 */
291
static int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,
292
				    const char *email, timestamp_t timestamp, int tz,
293
				    const char *message, void *cb_data)
Junio C Hamano's avatar
Junio C Hamano committed
294
{
295
	struct expire_reflog_policy_cb *cb = cb_data;
296
	struct commit *old_commit, *new_commit;
Junio C Hamano's avatar
Junio C Hamano committed
297

298
	if (timestamp < cb->cmd.expire_total)
299
		return 1;
300

301
	old_commit = new_commit = NULL;
302
	if (cb->cmd.stalefix &&
303
	    (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))
304
		return 1;
Junio C Hamano's avatar
Junio C Hamano committed
305

306
	if (timestamp < cb->cmd.expire_unreachable) {
307
		if (cb->unreachable_expire_kind == UE_ALWAYS)
308
			return 1;
309
		if (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))
310
			return 1;
311
	}
Junio C Hamano's avatar
Junio C Hamano committed
312

313
	if (cb->cmd.recno && --(cb->cmd.recno) == 0)
314 315
		return 1;

Junio C Hamano's avatar
Junio C Hamano committed
316
	return 0;
317 318
}

319
static int push_tip_to_list(const char *refname, const struct object_id *oid,
320
			    int flags, void *cb_data)
321 322 323 324 325
{
	struct commit_list **list = cb_data;
	struct commit *tip_commit;
	if (flags & REF_ISSYMREF)
		return 0;
326
	tip_commit = lookup_commit_reference_gently(the_repository, oid, 1);
327 328 329 330 331 332
	if (!tip_commit)
		return 0;
	commit_list_insert(tip_commit, list);
	return 0;
}

333
static void reflog_expiry_prepare(const char *refname,
334
				  const struct object_id *oid,
335
				  void *cb_data)
336
{
337 338 339
	struct expire_reflog_policy_cb *cb = cb_data;

	if (!cb->cmd.expire_unreachable || !strcmp(refname, "HEAD")) {
340 341 342
		cb->tip_commit = NULL;
		cb->unreachable_expire_kind = UE_HEAD;
	} else {
343 344
		cb->tip_commit = lookup_commit_reference_gently(the_repository,
								oid, 1);
345 346 347 348 349 350
		if (!cb->tip_commit)
			cb->unreachable_expire_kind = UE_ALWAYS;
		else
			cb->unreachable_expire_kind = UE_NORMAL;
	}

351
	if (cb->cmd.expire_unreachable <= cb->cmd.expire_total)
352 353 354 355 356 357 358
		cb->unreachable_expire_kind = UE_ALWAYS;

	cb->mark_list = NULL;
	cb->tips = NULL;
	if (cb->unreachable_expire_kind != UE_ALWAYS) {
		if (cb->unreachable_expire_kind == UE_HEAD) {
			struct commit_list *elem;
359

360
			for_each_ref(push_tip_to_list, &cb->tips);
361 362 363 364 365
			for (elem = cb->tips; elem; elem = elem->next)
				commit_list_insert(elem->item, &cb->mark_list);
		} else {
			commit_list_insert(cb->tip_commit, &cb->mark_list);
		}
366
		cb->mark_limit = cb->cmd.expire_total;
367 368 369 370
		mark_reachable(cb);
	}
}

371
static void reflog_expiry_cleanup(void *cb_data)
372
{
373 374
	struct expire_reflog_policy_cb *cb = cb_data;

375 376 377 378 379 380 381 382 383 384 385 386
	if (cb->unreachable_expire_kind != UE_ALWAYS) {
		if (cb->unreachable_expire_kind == UE_HEAD) {
			struct commit_list *elem;
			for (elem = cb->tips; elem; elem = elem->next)
				clear_commit_marks(elem->item, REACHABLE);
			free_commit_list(cb->tips);
		} else {
			clear_commit_marks(cb->tip_commit, REACHABLE);
		}
	}
}

387
static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data)
388 389 390 391
{
	struct collected_reflog *e;
	struct collect_reflog_cb *cb = cb_data;

392
	FLEX_ALLOC_STR(e, reflog, ref);
393
	oidcpy(&e->oid, oid);
394 395 396 397 398
	ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
	cb->e[cb->nr++] = e;
	return 0;
}

399 400
static struct reflog_expire_cfg {
	struct reflog_expire_cfg *next;
401 402
	timestamp_t expire_total;
	timestamp_t expire_unreachable;
403 404 405 406
	char pattern[FLEX_ARRAY];
} *reflog_expire_cfg, **reflog_expire_cfg_tail;

static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
407
{
408 409 410 411 412 413
	struct reflog_expire_cfg *ent;

	if (!reflog_expire_cfg_tail)
		reflog_expire_cfg_tail = &reflog_expire_cfg;

	for (ent = reflog_expire_cfg; ent; ent = ent->next)
414 415
		if (!strncmp(ent->pattern, pattern, len) &&
		    ent->pattern[len] == '\0')
416 417
			return ent;

418
	FLEX_ALLOC_MEM(ent, pattern, pattern, len);
419 420 421 422 423 424 425 426 427 428 429
	*reflog_expire_cfg_tail = ent;
	reflog_expire_cfg_tail = &(ent->next);
	return ent;
}

/* expiry timer slot */
#define EXPIRE_TOTAL   01
#define EXPIRE_UNREACH 02

static int reflog_expire_config(const char *var, const char *value, void *cb)
{
430 431
	const char *pattern, *key;
	int pattern_len;
432
	timestamp_t expire;
433 434 435
	int slot;
	struct reflog_expire_cfg *ent;

436
	if (parse_config_key(var, "gc", &pattern, &pattern_len, &key) < 0)
437 438
		return git_default_config(var, value, cb);

439
	if (!strcmp(key, "reflogexpire")) {
440
		slot = EXPIRE_TOTAL;
441
		if (git_config_expiry_date(&expire, var, value))
442
			return -1;
443
	} else if (!strcmp(key, "reflogexpireunreachable")) {
444
		slot = EXPIRE_UNREACH;
445
		if (git_config_expiry_date(&expire, var, value))
446 447 448 449
			return -1;
	} else
		return git_default_config(var, value, cb);

450
	if (!pattern) {
451 452 453 454 455 456 457 458
		switch (slot) {
		case EXPIRE_TOTAL:
			default_reflog_expire = expire;
			break;
		case EXPIRE_UNREACH:
			default_reflog_expire_unreachable = expire;
			break;
		}
459 460
		return 0;
	}
461

462
	ent = find_cfg_ent(pattern, pattern_len);
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
	if (!ent)
		return -1;
	switch (slot) {
	case EXPIRE_TOTAL:
		ent->expire_total = expire;
		break;
	case EXPIRE_UNREACH:
		ent->expire_unreachable = expire;
		break;
	}
	return 0;
}

static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref)
{
	struct reflog_expire_cfg *ent;

	if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH))
		return; /* both given explicitly -- nothing to tweak */

	for (ent = reflog_expire_cfg; ent; ent = ent->next) {
484
		if (!wildmatch(ent->pattern, ref, 0)) {
485 486 487 488 489 490 491 492
			if (!(slot & EXPIRE_TOTAL))
				cb->expire_total = ent->expire_total;
			if (!(slot & EXPIRE_UNREACH))
				cb->expire_unreachable = ent->expire_unreachable;
			return;
		}
	}

493 494 495 496 497 498 499 500 501 502 503
	/*
	 * If unconfigured, make stash never expire
	 */
	if (!strcmp(ref, "refs/stash")) {
		if (!(slot & EXPIRE_TOTAL))
			cb->expire_total = 0;
		if (!(slot & EXPIRE_UNREACH))
			cb->expire_unreachable = 0;
		return;
	}

504 505 506 507 508
	/* Nothing matched -- use the default value */
	if (!(slot & EXPIRE_TOTAL))
		cb->expire_total = default_reflog_expire;
	if (!(slot & EXPIRE_UNREACH))
		cb->expire_unreachable = default_reflog_expire_unreachable;
509 510
}

Junio C Hamano's avatar
Junio C Hamano committed
511 512
static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
{
513
	struct expire_reflog_policy_cb cb;
514
	timestamp_t now = time(NULL);
Junio C Hamano's avatar
Junio C Hamano committed
515
	int i, status, do_all;
516
	int explicit_expiry = 0;
517
	unsigned int flags = 0;
Junio C Hamano's avatar
Junio C Hamano committed
518

519 520
	default_reflog_expire_unreachable = now - 30 * 24 * 3600;
	default_reflog_expire = now - 90 * 24 * 3600;
521
	git_config(reflog_expire_config, NULL);
522

Junio C Hamano's avatar
Junio C Hamano committed
523 524 525
	save_commit_buffer = 0;
	do_all = status = 0;
	memset(&cb, 0, sizeof(cb));
526

527 528
	cb.cmd.expire_total = default_reflog_expire;
	cb.cmd.expire_unreachable = default_reflog_expire_unreachable;
Junio C Hamano's avatar
Junio C Hamano committed
529 530 531 532

	for (i = 1; i < argc; i++) {
		const char *arg = argv[i];
		if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
533
			flags |= EXPIRE_REFLOGS_DRY_RUN;
534
		else if (starts_with(arg, "--expire=")) {
535
			if (parse_expiry_date(arg + 9, &cb.cmd.expire_total))
536
				die(_("'%s' is not a valid timestamp"), arg);
537 538
			explicit_expiry |= EXPIRE_TOTAL;
		}
539
		else if (starts_with(arg, "--expire-unreachable=")) {
540
			if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable))
541
				die(_("'%s' is not a valid timestamp"), arg);
542 543
			explicit_expiry |= EXPIRE_UNREACH;
		}
544
		else if (!strcmp(arg, "--stale-fix"))
545
			cb.cmd.stalefix = 1;
546
		else if (!strcmp(arg, "--rewrite"))
547
			flags |= EXPIRE_REFLOGS_REWRITE;
548
		else if (!strcmp(arg, "--updateref"))
549
			flags |= EXPIRE_REFLOGS_UPDATE_REF;
Junio C Hamano's avatar
Junio C Hamano committed
550 551
		else if (!strcmp(arg, "--all"))
			do_all = 1;
552
		else if (!strcmp(arg, "--verbose"))
553
			flags |= EXPIRE_REFLOGS_VERBOSE;
Junio C Hamano's avatar
Junio C Hamano committed
554 555 556 557 558 559 560 561 562
		else if (!strcmp(arg, "--")) {
			i++;
			break;
		}
		else if (arg[0] == '-')
			usage(reflog_expire_usage);
		else
			break;
	}
563 564 565 566 567 568

	/*
	 * We can trust the commits and objects reachable from refs
	 * even in older repository.  We cannot trust what's reachable
	 * from reflog if the repository was pruned with older git.
	 */
569 570
	if (cb.cmd.stalefix) {
		init_revisions(&cb.cmd.revs, prefix);
571
		if (flags & EXPIRE_REFLOGS_VERBOSE)
572
			printf("Marking reachable objects...");
573
		mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL);
574
		if (flags & EXPIRE_REFLOGS_VERBOSE)
575 576 577
			putchar('\n');
	}

578 579 580 581 582
	if (do_all) {
		struct collect_reflog_cb collected;
		int i;

		memset(&collected, 0, sizeof(collected));
583
		for_each_reflog(collect_reflog, &collected);
584 585
		for (i = 0; i < collected.nr; i++) {
			struct collected_reflog *e = collected.e[i];
586
			set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog);
587
			status |= reflog_expire(e->reflog, &e->oid, flags,
588 589 590 591
						reflog_expiry_prepare,
						should_expire_reflog_ent,
						reflog_expiry_cleanup,
						&cb);
592 593 594 595 596
			free(e);
		}
		free(collected.e);
	}

597 598
	for (; i < argc; i++) {
		char *ref;
599
		struct object_id oid;
600
		if (!dwim_log(argv[i], strlen(argv[i]), &oid, &ref)) {
601
			status |= error("%s points nowhere!", argv[i]);
Junio C Hamano's avatar
Junio C Hamano committed
602 603
			continue;
		}
604
		set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref);
605
		status |= reflog_expire(ref, &oid, flags,
606 607 608 609
					reflog_expiry_prepare,
					should_expire_reflog_ent,
					reflog_expiry_cleanup,
					&cb);
Junio C Hamano's avatar
Junio C Hamano committed
610 611 612 613
	}
	return status;
}

614
static int count_reflog_ent(struct object_id *ooid, struct object_id *noid,
615
		const char *email, timestamp_t timestamp, int tz,
616 617
		const char *message, void *cb_data)
{
618 619 620
	struct expire_reflog_policy_cb *cb = cb_data;
	if (!cb->cmd.expire_total || timestamp < cb->cmd.expire_total)
		cb->cmd.recno++;
621 622 623 624 625
	return 0;
}

static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
{
626
	struct expire_reflog_policy_cb cb;
627
	int i, status = 0;
628
	unsigned int flags = 0;
629 630 631 632

	memset(&cb, 0, sizeof(cb));

	for (i = 1; i < argc; i++) {
633 634
		const char *arg = argv[i];
		if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
635
			flags |= EXPIRE_REFLOGS_DRY_RUN;
636
		else if (!strcmp(arg, "--rewrite"))
637
			flags |= EXPIRE_REFLOGS_REWRITE;
638
		else if (!strcmp(arg, "--updateref"))
639
			flags |= EXPIRE_REFLOGS_UPDATE_REF;
640
		else if (!strcmp(arg, "--verbose"))
641
			flags |= EXPIRE_REFLOGS_VERBOSE;
642 643 644 645 646 647 648 649 650 651 652 653 654 655
		else if (!strcmp(arg, "--")) {
			i++;
			break;
		}
		else if (arg[0] == '-')
			usage(reflog_delete_usage);
		else
			break;
	}

	if (argc - i < 1)
		return error("Nothing to delete?");

	for ( ; i < argc; i++) {
656
		const char *spec = strstr(argv[i], "@{");
657
		struct object_id oid;
658 659 660 661
		char *ep, *ref;
		int recno;

		if (!spec) {
662
			status |= error("Not a reflog: %s", argv[i]);
663 664 665
			continue;
		}

666
		if (!dwim_log(argv[i], spec - argv[i], &oid, &ref)) {
667
			status |= error("no reflog for '%s'", argv[i]);
668 669 670 671 672
			continue;
		}

		recno = strtoul(spec + 2, &ep, 10);
		if (*ep == '}') {
673
			cb.cmd.recno = -recno;
674 675
			for_each_reflog_ent(ref, count_reflog_ent, &cb);
		} else {
676
			cb.cmd.expire_total = approxidate(spec + 2);
677
			for_each_reflog_ent(ref, count_reflog_ent, &cb);
678
			cb.cmd.expire_total = 0;
679 680
		}

681
		status |= reflog_expire(ref, &oid, flags,
682 683 684 685
					reflog_expiry_prepare,
					should_expire_reflog_ent,
					reflog_expiry_cleanup,
					&cb);
686 687 688 689 690
		free(ref);
	}
	return status;
}

691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
static int cmd_reflog_exists(int argc, const char **argv, const char *prefix)
{
	int i, start = 0;

	for (i = 1; i < argc; i++) {
		const char *arg = argv[i];
		if (!strcmp(arg, "--")) {
			i++;
			break;
		}
		else if (arg[0] == '-')
			usage(reflog_exists_usage);
		else
			break;
	}

	start = i;

	if (argc - start != 1)
		usage(reflog_exists_usage);

	if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL))
		die("invalid ref format: %s", argv[start]);
	return !reflog_exists(argv[start]);
}

717 718 719 720
/*
 * main "reflog"
 */

Junio C Hamano's avatar
Junio C Hamano committed
721
static const char reflog_usage[] =
722
"git reflog [ show | expire | delete | exists ]";
Junio C Hamano's avatar
Junio C Hamano committed
723 724 725

int cmd_reflog(int argc, const char **argv, const char *prefix)
{
726 727 728
	if (argc > 1 && !strcmp(argv[1], "-h"))
		usage(reflog_usage);

Linus Torvalds's avatar
Linus Torvalds committed
729 730 731 732 733 734 735 736
	/* With no command, we default to showing it. */
	if (argc < 2 || *argv[1] == '-')
		return cmd_log_reflog(argc, argv, prefix);

	if (!strcmp(argv[1], "show"))
		return cmd_log_reflog(argc - 1, argv + 1, prefix);

	if (!strcmp(argv[1], "expire"))
Junio C Hamano's avatar
Junio C Hamano committed
737
		return cmd_reflog_expire(argc - 1, argv + 1, prefix);
Linus Torvalds's avatar
Linus Torvalds committed
738

739 740 741
	if (!strcmp(argv[1], "delete"))
		return cmd_reflog_delete(argc - 1, argv + 1, prefix);

742 743 744
	if (!strcmp(argv[1], "exists"))
		return cmd_reflog_exists(argc - 1, argv + 1, prefix);

745
	return cmd_log_reflog(argc, argv, prefix);
Junio C Hamano's avatar
Junio C Hamano committed
746
}