Skip to content

Commit b4ca1db

Browse files
committed
reflog --expire-unreachable: avoid merge-base computation
The option tells the command to expire older reflog entries that refer to commits that are no longer reachable from the tip of the ref the reflog is associated with. To avoid repeated merge_base() invocations, we used to mark commits that are known to be reachable by walking the history from the tip until we hit commits that are older than expire-total (which is the timestamp before which all the reflog entries are expired). However, it is a different matter if a commit is _not_ known to be reachable and the commit is known to be unreachable. Because you can rewind a ref to an ancient commit and then reset it back to the original tip, a recent reflog entry can point at a commit that older than the expire-total timestamp and we shouldn't expire it. For that reason, we had to run merge-base computation when a commit is _not_ known to be reachable. This introduces a lazy/on-demand traversal of the history to mark reachable commits in steps. As before, we mark commits that are newer than expire-total to optimize the normal case before walking reflog, but we dig deeper from the commits the initial step left off when we encounter a commit that is not known to be reachable. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 902f235 commit b4ca1db

File tree

1 file changed

+56
-40
lines changed

1 file changed

+56
-40
lines changed

builtin-reflog.c

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ struct expire_reflog_cb {
3636
FILE *newlog;
3737
const char *ref;
3838
struct commit *ref_commit;
39+
struct commit_list *mark_list;
40+
unsigned long mark_limit;
3941
struct cmd_reflog_expire_cb *cmd;
4042
unsigned char last_kept_sha1[20];
4143
};
@@ -210,46 +212,23 @@ static int keep_entry(struct commit **it, unsigned char *sha1)
210212
return 1;
211213
}
212214

213-
static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
215+
/*
216+
* Starting from commits in the cb->mark_list, mark commits that are
217+
* reachable from them. Stop the traversal at commits older than
218+
* the expire_limit and queue them back, so that the caller can call
219+
* us again to restart the traversal with longer expire_limit.
220+
*/
221+
static void mark_reachable(struct expire_reflog_cb *cb)
214222
{
215-
/*
216-
* We may or may not have the commit yet - if not, look it
217-
* up using the supplied sha1.
218-
*/
219-
if (!commit) {
220-
if (is_null_sha1(sha1))
221-
return 0;
222-
223-
commit = lookup_commit_reference_gently(sha1, 1);
224-
225-
/* Not a commit -- keep it */
226-
if (!commit)
227-
return 0;
228-
}
229-
230-
/* Reachable from the current ref? Don't prune. */
231-
if (commit->object.flags & REACHABLE)
232-
return 0;
233-
if (in_merge_bases(commit, &cb->ref_commit, 1))
234-
return 0;
235-
236-
/* We can't reach it - prune it. */
237-
return 1;
238-
}
223+
struct commit *commit;
224+
struct commit_list *pending;
225+
unsigned long expire_limit = cb->mark_limit;
226+
struct commit_list *leftover = NULL;
239227

240-
static void mark_reachable(struct commit *commit, unsigned long expire_limit)
241-
{
242-
/*
243-
* We need to compute whether the commit on either side of a reflog
244-
* entry is reachable from the tip of the ref for all entries.
245-
* Mark commits that are reachable from the tip down to the
246-
* time threshold first; we know a commit marked thusly is
247-
* reachable from the tip without running in_merge_bases()
248-
* at all.
249-
*/
250-
struct commit_list *pending = NULL;
228+
for (pending = cb->mark_list; pending; pending = pending->next)
229+
pending->item->object.flags &= ~REACHABLE;
251230

252-
commit_list_insert(commit, &pending);
231+
pending = cb->mark_list;
253232
while (pending) {
254233
struct commit_list *entry = pending;
255234
struct commit_list *parent;
@@ -261,8 +240,11 @@ static void mark_reachable(struct commit *commit, unsigned long expire_limit)
261240
if (parse_commit(commit))
262241
continue;
263242
commit->object.flags |= REACHABLE;
264-
if (commit->date < expire_limit)
243+
if (commit->date < expire_limit) {
244+
commit_list_insert(commit, &leftover);
265245
continue;
246+
}
247+
commit->object.flags |= REACHABLE;
266248
parent = commit->parents;
267249
while (parent) {
268250
commit = parent->item;
@@ -272,6 +254,36 @@ static void mark_reachable(struct commit *commit, unsigned long expire_limit)
272254
commit_list_insert(commit, &pending);
273255
}
274256
}
257+
cb->mark_list = leftover;
258+
}
259+
260+
static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
261+
{
262+
/*
263+
* We may or may not have the commit yet - if not, look it
264+
* up using the supplied sha1.
265+
*/
266+
if (!commit) {
267+
if (is_null_sha1(sha1))
268+
return 0;
269+
270+
commit = lookup_commit_reference_gently(sha1, 1);
271+
272+
/* Not a commit -- keep it */
273+
if (!commit)
274+
return 0;
275+
}
276+
277+
/* Reachable from the current ref? Don't prune. */
278+
if (commit->object.flags & REACHABLE)
279+
return 0;
280+
281+
if (cb->mark_list && cb->mark_limit) {
282+
cb->mark_limit = 0; /* dig down to the root */
283+
mark_reachable(cb);
284+
}
285+
286+
return !(commit->object.flags & REACHABLE);
275287
}
276288

277289
static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
@@ -348,8 +360,12 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
348360
cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
349361
cb.ref = ref;
350362
cb.cmd = cmd;
351-
if (cb.ref_commit)
352-
mark_reachable(cb.ref_commit, cmd->expire_total);
363+
if (cb.ref_commit) {
364+
cb.mark_list = NULL;
365+
commit_list_insert(cb.ref_commit, &cb.mark_list);
366+
cb.mark_limit = cmd->expire_total;
367+
mark_reachable(&cb);
368+
}
353369
for_each_reflog_ent(ref, expire_reflog_ent, &cb);
354370
if (cb.ref_commit)
355371
clear_commit_marks(cb.ref_commit, REACHABLE);

0 commit comments

Comments
 (0)