reflog: libify delete reflog function and helpers

Currently stash shells out to reflog in order to delete refs. In an effort to reduce how much we shell out to a subprocess, libify the functionality that stash needs into reflog.c. Add a reflog_delete function that is pretty much the logic in the while loop in builtin/reflog.c cmd_reflog_delete(). This is a function that builtin/reflog.c and builtin/stash.c can both call. Also move functions needed by reflog_delete and export them. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: John Cai <johncai86@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

John Cai committed Mar 2, 2022 at 22:27 UTC 7d3d226e700904d6fbf3d9a1b351ebeb02f2cf04
5 files changed +481 -452
Makefile
+1
@@ -989,6 +989,7 @@ LIB_OBJS += rebase-interactive.o
989 LIB_OBJS += rebase.o
990 LIB_OBJS += ref-filter.o
991 LIB_OBJS += reflog-walk.o
992 +LIB_OBJS += reflog.o
993 LIB_OBJS += refs.o
994 LIB_OBJS += refs/debug.o
995 LIB_OBJS += refs/files-backend.o
builtin/reflog.c
+4 -451
@@ -1,16 +1,9 @@
1 #include "builtin.h"
2 #include "config.h"
3 -#include "lockfile.h"
4 -#include "object-store.h"
5 -#include "repository.h"
6 -#include "commit.h"
7 -#include "refs.h"
8 -#include "dir.h"
9 -#include "tree-walk.h"
10 -#include "diff.h"
3 #include "revision.h"
4 #include "reachable.h"
5 #include "worktree.h"
6 +#include "reflog.h"
7
8 static const char reflog_exists_usage[] =
9 N_("git reflog exists <ref>");
@@ -18,404 +11,11 @@ N_("git reflog exists <ref>");
11 static timestamp_t default_reflog_expire;
12 static timestamp_t default_reflog_expire_unreachable;
13
21 -struct cmd_reflog_expire_cb {
22 - int stalefix;
23 - int explicit_expiry;
24 - timestamp_t expire_total;
25 - timestamp_t expire_unreachable;
26 - int recno;
27 -};
28 -
29 -struct expire_reflog_policy_cb {
30 - enum {
31 - UE_NORMAL,
32 - UE_ALWAYS,
33 - UE_HEAD
34 - } unreachable_expire_kind;
35 - struct commit_list *mark_list;
36 - unsigned long mark_limit;
37 - struct cmd_reflog_expire_cb cmd;
38 - struct commit *tip_commit;
39 - struct commit_list *tips;
40 - unsigned int dry_run:1;
41 -};
42 -
14 struct worktree_reflogs {
15 struct worktree *worktree;
16 struct string_list reflogs;
17 };
18
48 -/* Remember to update object flag allocation in object.h */
49 -#define INCOMPLETE (1u<<10)
50 -#define STUDYING (1u<<11)
51 -#define REACHABLE (1u<<12)
52 -
53 -static int tree_is_complete(const struct object_id *oid)
54 -{
55 - struct tree_desc desc;
56 - struct name_entry entry;
57 - int complete;
58 - struct tree *tree;
59 -
60 - tree = lookup_tree(the_repository, oid);
61 - if (!tree)
62 - return 0;
63 - if (tree->object.flags & SEEN)
64 - return 1;
65 - if (tree->object.flags & INCOMPLETE)
66 - return 0;
67 -
68 - if (!tree->buffer) {
69 - enum object_type type;
70 - unsigned long size;
71 - void *data = read_object_file(oid, &type, &size);
72 - if (!data) {
73 - tree->object.flags |= INCOMPLETE;
74 - return 0;
75 - }
76 - tree->buffer = data;
77 - tree->size = size;
78 - }
79 - init_tree_desc(&desc, tree->buffer, tree->size);
80 - complete = 1;
81 - while (tree_entry(&desc, &entry)) {
82 - if (!has_object_file(&entry.oid) ||
83 - (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {
84 - tree->object.flags |= INCOMPLETE;
85 - complete = 0;
86 - }
87 - }
88 - free_tree_buffer(tree);
89 -
90 - if (complete)
91 - tree->object.flags |= SEEN;
92 - return complete;
93 -}
94 -
95 -static int commit_is_complete(struct commit *commit)
96 -{
97 - struct object_array study;
98 - struct object_array found;
99 - int is_incomplete = 0;
100 - int i;
101 -
102 - /* early return */
103 - if (commit->object.flags & SEEN)
104 - return 1;
105 - if (commit->object.flags & INCOMPLETE)
106 - return 0;
107 - /*
108 - * Find all commits that are reachable and are not marked as
109 - * SEEN. Then make sure the trees and blobs contained are
110 - * complete. After that, mark these commits also as SEEN.
111 - * If some of the objects that are needed to complete this
112 - * commit are missing, mark this commit as INCOMPLETE.
113 - */
114 - memset(&study, 0, sizeof(study));
115 - memset(&found, 0, sizeof(found));
116 - add_object_array(&commit->object, NULL, &study);
117 - add_object_array(&commit->object, NULL, &found);
118 - commit->object.flags |= STUDYING;
119 - while (study.nr) {
120 - struct commit *c;
121 - struct commit_list *parent;
122 -
123 - c = (struct commit *)object_array_pop(&study);
124 - if (!c->object.parsed && !parse_object(the_repository, &c->object.oid))
125 - c->object.flags |= INCOMPLETE;
126 -
127 - if (c->object.flags & INCOMPLETE) {
128 - is_incomplete = 1;
129 - break;
130 - }
131 - else if (c->object.flags & SEEN)
132 - continue;
133 - for (parent = c->parents; parent; parent = parent->next) {
134 - struct commit *p = parent->item;
135 - if (p->object.flags & STUDYING)
136 - continue;
137 - p->object.flags |= STUDYING;
138 - add_object_array(&p->object, NULL, &study);
139 - add_object_array(&p->object, NULL, &found);
140 - }
141 - }
142 - if (!is_incomplete) {
143 - /*
144 - * make sure all commits in "found" array have all the
145 - * necessary objects.
146 - */
147 - for (i = 0; i < found.nr; i++) {
148 - struct commit *c =
149 - (struct commit *)found.objects[i].item;
150 - if (!tree_is_complete(get_commit_tree_oid(c))) {
151 - is_incomplete = 1;
152 - c->object.flags |= INCOMPLETE;
153 - }
154 - }
155 - if (!is_incomplete) {
156 - /* mark all found commits as complete, iow SEEN */
157 - for (i = 0; i < found.nr; i++)
158 - found.objects[i].item->flags |= SEEN;
159 - }
160 - }
161 - /* clear flags from the objects we traversed */
162 - for (i = 0; i < found.nr; i++)
163 - found.objects[i].item->flags &= ~STUDYING;
164 - if (is_incomplete)
165 - commit->object.flags |= INCOMPLETE;
166 - else {
167 - /*
168 - * If we come here, we have (1) traversed the ancestry chain
169 - * from the "commit" until we reach SEEN commits (which are
170 - * known to be complete), and (2) made sure that the commits
171 - * encountered during the above traversal refer to trees that
172 - * are complete. Which means that we know *all* the commits
173 - * we have seen during this process are complete.
174 - */
175 - for (i = 0; i < found.nr; i++)
176 - found.objects[i].item->flags |= SEEN;
177 - }
178 - /* free object arrays */
179 - object_array_clear(&study);
180 - object_array_clear(&found);
181 - return !is_incomplete;
182 -}
183 -
184 -static int keep_entry(struct commit **it, struct object_id *oid)
185 -{
186 - struct commit *commit;
187 -
188 - if (is_null_oid(oid))
189 - return 1;
190 - commit = lookup_commit_reference_gently(the_repository, oid, 1);
191 - if (!commit)
192 - return 0;
193 -
194 - /*
195 - * Make sure everything in this commit exists.
196 - *
197 - * We have walked all the objects reachable from the refs
198 - * and cache earlier. The commits reachable by this commit
199 - * must meet SEEN commits -- and then we should mark them as
200 - * SEEN as well.
201 - */
202 - if (!commit_is_complete(commit))
203 - return 0;
204 - *it = commit;
205 - return 1;
206 -}
207 -
208 -/*
209 - * Starting from commits in the cb->mark_list, mark commits that are
210 - * reachable from them. Stop the traversal at commits older than
211 - * the expire_limit and queue them back, so that the caller can call
212 - * us again to restart the traversal with longer expire_limit.
213 - */
214 -static void mark_reachable(struct expire_reflog_policy_cb *cb)
215 -{
216 - struct commit_list *pending;
217 - timestamp_t expire_limit = cb->mark_limit;
218 - struct commit_list *leftover = NULL;
219 -
220 - for (pending = cb->mark_list; pending; pending = pending->next)
221 - pending->item->object.flags &= ~REACHABLE;
222 -
223 - pending = cb->mark_list;
224 - while (pending) {
225 - struct commit_list *parent;
226 - struct commit *commit = pop_commit(&pending);
227 - if (commit->object.flags & REACHABLE)
228 - continue;
229 - if (parse_commit(commit))
230 - continue;
231 - commit->object.flags |= REACHABLE;
232 - if (commit->date < expire_limit) {
233 - commit_list_insert(commit, &leftover);
234 - continue;
235 - }
236 - commit->object.flags |= REACHABLE;
237 - parent = commit->parents;
238 - while (parent) {
239 - commit = parent->item;
240 - parent = parent->next;
241 - if (commit->object.flags & REACHABLE)
242 - continue;
243 - commit_list_insert(commit, &pending);
244 - }
245 - }
246 - cb->mark_list = leftover;
247 -}
248 -
249 -static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)
250 -{
251 - /*
252 - * We may or may not have the commit yet - if not, look it
253 - * up using the supplied sha1.
254 - */
255 - if (!commit) {
256 - if (is_null_oid(oid))
257 - return 0;
258 -
259 - commit = lookup_commit_reference_gently(the_repository, oid,
260 - 1);
261 -
262 - /* Not a commit -- keep it */
263 - if (!commit)
264 - return 0;
265 - }
266 -
267 - /* Reachable from the current ref? Don't prune. */
268 - if (commit->object.flags & REACHABLE)
269 - return 0;
270 -
271 - if (cb->mark_list && cb->mark_limit) {
272 - cb->mark_limit = 0; /* dig down to the root */
273 - mark_reachable(cb);
274 - }
275 -
276 - return !(commit->object.flags & REACHABLE);
277 -}
278 -
279 -/*
280 - * Return true iff the specified reflog entry should be expired.
281 - */
282 -static int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,
283 - const char *email, timestamp_t timestamp, int tz,
284 - const char *message, void *cb_data)
285 -{
286 - struct expire_reflog_policy_cb *cb = cb_data;
287 - struct commit *old_commit, *new_commit;
288 -
289 - if (timestamp < cb->cmd.expire_total)
290 - return 1;
291 -
292 - old_commit = new_commit = NULL;
293 - if (cb->cmd.stalefix &&
294 - (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))
295 - return 1;
296 -
297 - if (timestamp < cb->cmd.expire_unreachable) {
298 - switch (cb->unreachable_expire_kind) {
299 - case UE_ALWAYS:
300 - return 1;
301 - case UE_NORMAL:
302 - case UE_HEAD:
303 - if (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))
304 - return 1;
305 - break;
306 - }
307 - }
308 -
309 - if (cb->cmd.recno && --(cb->cmd.recno) == 0)
310 - return 1;
311 -
312 - return 0;
313 -}
314 -
315 -static int should_expire_reflog_ent_verbose(struct object_id *ooid,
316 - struct object_id *noid,
317 - const char *email,
318 - timestamp_t timestamp, int tz,
319 - const char *message, void *cb_data)
320 -{
321 - struct expire_reflog_policy_cb *cb = cb_data;
322 - int expire;
323 -
324 - expire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz,
325 - message, cb);
326 -
327 - if (!expire)
328 - printf("keep %s", message);
329 - else if (cb->dry_run)
330 - printf("would prune %s", message);
331 - else
332 - printf("prune %s", message);
333 -
334 - return expire;
335 -}
336 -
337 -static int push_tip_to_list(const char *refname, const struct object_id *oid,
338 - int flags, void *cb_data)
339 -{
340 - struct commit_list **list = cb_data;
341 - struct commit *tip_commit;
342 - if (flags & REF_ISSYMREF)
343 - return 0;
344 - tip_commit = lookup_commit_reference_gently(the_repository, oid, 1);
345 - if (!tip_commit)
346 - return 0;
347 - commit_list_insert(tip_commit, list);
348 - return 0;
349 -}
350 -
351 -static int is_head(const char *refname)
352 -{
353 - switch (ref_type(refname)) {
354 - case REF_TYPE_OTHER_PSEUDOREF:
355 - case REF_TYPE_MAIN_PSEUDOREF:
356 - if (parse_worktree_ref(refname, NULL, NULL, &refname))
357 - BUG("not a worktree ref: %s", refname);
358 - break;
359 - default:
360 - break;
361 - }
362 - return !strcmp(refname, "HEAD");
363 -}
364 -
365 -static void reflog_expiry_prepare(const char *refname,
366 - const struct object_id *oid,
367 - void *cb_data)
368 -{
369 - struct expire_reflog_policy_cb *cb = cb_data;
370 - struct commit_list *elem;
371 - struct commit *commit = NULL;
372 -
373 - if (!cb->cmd.expire_unreachable || is_head(refname)) {
374 - cb->unreachable_expire_kind = UE_HEAD;
375 - } else {
376 - commit = lookup_commit(the_repository, oid);
377 - cb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS;
378 - }
379 -
380 - if (cb->cmd.expire_unreachable <= cb->cmd.expire_total)
381 - cb->unreachable_expire_kind = UE_ALWAYS;
382 -
383 - switch (cb->unreachable_expire_kind) {
384 - case UE_ALWAYS:
385 - return;
386 - case UE_HEAD:
387 - for_each_ref(push_tip_to_list, &cb->tips);
388 - for (elem = cb->tips; elem; elem = elem->next)
389 - commit_list_insert(elem->item, &cb->mark_list);
390 - break;
391 - case UE_NORMAL:
392 - commit_list_insert(commit, &cb->mark_list);
393 - /* For reflog_expiry_cleanup() below */
394 - cb->tip_commit = commit;
395 - }
396 - cb->mark_limit = cb->cmd.expire_total;
397 - mark_reachable(cb);
398 -}
399 -
400 -static void reflog_expiry_cleanup(void *cb_data)
401 -{
402 - struct expire_reflog_policy_cb *cb = cb_data;
403 - struct commit_list *elem;
404 -
405 - switch (cb->unreachable_expire_kind) {
406 - case UE_ALWAYS:
407 - return;
408 - case UE_HEAD:
409 - for (elem = cb->tips; elem; elem = elem->next)
410 - clear_commit_marks(elem->item, REACHABLE);
411 - free_commit_list(cb->tips);
412 - break;
413 - case UE_NORMAL:
414 - clear_commit_marks(cb->tip_commit, REACHABLE);
415 - break;
416 - }
417 -}
418 -
19 static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data)
20 {
21 struct worktree_reflogs *cb = cb_data;
@@ -704,16 +304,6 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
304 return status;
305 }
306
707 -static int count_reflog_ent(struct object_id *ooid, struct object_id *noid,
708 - const char *email, timestamp_t timestamp, int tz,
709 - const char *message, void *cb_data)
710 -{
711 - struct cmd_reflog_expire_cb *cb = cb_data;
712 - if (!cb->expire_total || timestamp < cb->expire_total)
713 - cb->recno++;
714 - return 0;
715 -}
716 -
307 static const char * reflog_delete_usage[] = {
308 N_("git reflog delete [--rewrite] [--updateref] "
309 "[--dry-run | -n] [--verbose] <refs>..."),
@@ -722,11 +312,10 @@ static const char * reflog_delete_usage[] = {
312
313 static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
314 {
725 - struct cmd_reflog_expire_cb cmd = { 0 };
315 int i, status = 0;
316 unsigned int flags = 0;
317 int verbose = 0;
729 - reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;
318 +
319 const struct option options[] = {
320 OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"),
321 EXPIRE_REFLOGS_DRY_RUN),
@@ -742,48 +331,12 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
331
332 argc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0);
333
745 - if (verbose)
746 - should_prune_fn = should_expire_reflog_ent_verbose;
747 -
334 if (argc < 1)
335 return error(_("no reflog specified to delete"));
336
751 - for (i = 0; i < argc; i++) {
752 - const char *spec = strstr(argv[i], "@{");
753 - char *ep, *ref;
754 - int recno;
755 - struct expire_reflog_policy_cb cb = {
756 - .dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),
757 - };
758 -
759 - if (!spec) {
760 - status |= error(_("not a reflog: %s"), argv[i]);
761 - continue;
762 - }
337 + for (i = 0; i < argc; i++)
338 + status |= reflog_delete(argv[i], flags, verbose);
339
764 - if (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) {
765 - status |= error(_("no reflog for '%s'"), argv[i]);
766 - continue;
767 - }
768 -
769 - recno = strtoul(spec + 2, &ep, 10);
770 - if (*ep == '}') {
771 - cmd.recno = -recno;
772 - for_each_reflog_ent(ref, count_reflog_ent, &cmd);
773 - } else {
774 - cmd.expire_total = approxidate(spec + 2);
775 - for_each_reflog_ent(ref, count_reflog_ent, &cmd);
776 - cmd.expire_total = 0;
777 - }
778 -
779 - cb.cmd = cmd;
780 - status |= reflog_expire(ref, flags,
781 - reflog_expiry_prepare,
782 - should_prune_fn,
783 - reflog_expiry_cleanup,
784 - &cb);
785 - free(ref);
786 - }
340 return status;
341 }
342
object.h
+1 -1
@@ -75,7 +75,7 @@ struct object_array {
75 * builtin/fsck.c: 0--3
76 * builtin/gc.c: 0
77 * builtin/index-pack.c: 2021
78 - * builtin/reflog.c: 10--12
78 + * reflog.c: 10--12
79 * builtin/show-branch.c: 0-------------------------------------------26
80 * builtin/unpack-objects.c: 2021
81 */
reflog.c new
+432
@@ -0,0 +1,432 @@
1 +#include "cache.h"
2 +#include "object-store.h"
3 +#include "reflog.h"
4 +#include "refs.h"
5 +#include "revision.h"
6 +#include "worktree.h"
7 +
8 +/* Remember to update object flag allocation in object.h */
9 +#define INCOMPLETE (1u<<10)
10 +#define STUDYING (1u<<11)
11 +#define REACHABLE (1u<<12)
12 +
13 +static int tree_is_complete(const struct object_id *oid)
14 +{
15 + struct tree_desc desc;
16 + struct name_entry entry;
17 + int complete;
18 + struct tree *tree;
19 +
20 + tree = lookup_tree(the_repository, oid);
21 + if (!tree)
22 + return 0;
23 + if (tree->object.flags & SEEN)
24 + return 1;
25 + if (tree->object.flags & INCOMPLETE)
26 + return 0;
27 +
28 + if (!tree->buffer) {
29 + enum object_type type;
30 + unsigned long size;
31 + void *data = read_object_file(oid, &type, &size);
32 + if (!data) {
33 + tree->object.flags |= INCOMPLETE;
34 + return 0;
35 + }
36 + tree->buffer = data;
37 + tree->size = size;
38 + }
39 + init_tree_desc(&desc, tree->buffer, tree->size);
40 + complete = 1;
41 + while (tree_entry(&desc, &entry)) {
42 + if (!has_object_file(&entry.oid) ||
43 + (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) {
44 + tree->object.flags |= INCOMPLETE;
45 + complete = 0;
46 + }
47 + }
48 + free_tree_buffer(tree);
49 +
50 + if (complete)
51 + tree->object.flags |= SEEN;
52 + return complete;
53 +}
54 +
55 +static int commit_is_complete(struct commit *commit)
56 +{
57 + struct object_array study;
58 + struct object_array found;
59 + int is_incomplete = 0;
60 + int i;
61 +
62 + /* early return */
63 + if (commit->object.flags & SEEN)
64 + return 1;
65 + if (commit->object.flags & INCOMPLETE)
66 + return 0;
67 + /*
68 + * Find all commits that are reachable and are not marked as
69 + * SEEN. Then make sure the trees and blobs contained are
70 + * complete. After that, mark these commits also as SEEN.
71 + * If some of the objects that are needed to complete this
72 + * commit are missing, mark this commit as INCOMPLETE.
73 + */
74 + memset(&study, 0, sizeof(study));
75 + memset(&found, 0, sizeof(found));
76 + add_object_array(&commit->object, NULL, &study);
77 + add_object_array(&commit->object, NULL, &found);
78 + commit->object.flags |= STUDYING;
79 + while (study.nr) {
80 + struct commit *c;
81 + struct commit_list *parent;
82 +
83 + c = (struct commit *)object_array_pop(&study);
84 + if (!c->object.parsed && !parse_object(the_repository, &c->object.oid))
85 + c->object.flags |= INCOMPLETE;
86 +
87 + if (c->object.flags & INCOMPLETE) {
88 + is_incomplete = 1;
89 + break;
90 + }
91 + else if (c->object.flags & SEEN)
92 + continue;
93 + for (parent = c->parents; parent; parent = parent->next) {
94 + struct commit *p = parent->item;
95 + if (p->object.flags & STUDYING)
96 + continue;
97 + p->object.flags |= STUDYING;
98 + add_object_array(&p->object, NULL, &study);
99 + add_object_array(&p->object, NULL, &found);
100 + }
101 + }
102 + if (!is_incomplete) {
103 + /*
104 + * make sure all commits in "found" array have all the
105 + * necessary objects.
106 + */
107 + for (i = 0; i < found.nr; i++) {
108 + struct commit *c =
109 + (struct commit *)found.objects[i].item;
110 + if (!tree_is_complete(get_commit_tree_oid(c))) {
111 + is_incomplete = 1;
112 + c->object.flags |= INCOMPLETE;
113 + }
114 + }
115 + if (!is_incomplete) {
116 + /* mark all found commits as complete, iow SEEN */
117 + for (i = 0; i < found.nr; i++)
118 + found.objects[i].item->flags |= SEEN;
119 + }
120 + }
121 + /* clear flags from the objects we traversed */
122 + for (i = 0; i < found.nr; i++)
123 + found.objects[i].item->flags &= ~STUDYING;
124 + if (is_incomplete)
125 + commit->object.flags |= INCOMPLETE;
126 + else {
127 + /*
128 + * If we come here, we have (1) traversed the ancestry chain
129 + * from the "commit" until we reach SEEN commits (which are
130 + * known to be complete), and (2) made sure that the commits
131 + * encountered during the above traversal refer to trees that
132 + * are complete. Which means that we know *all* the commits
133 + * we have seen during this process are complete.
134 + */
135 + for (i = 0; i < found.nr; i++)
136 + found.objects[i].item->flags |= SEEN;
137 + }
138 + /* free object arrays */
139 + object_array_clear(&study);
140 + object_array_clear(&found);
141 + return !is_incomplete;
142 +}
143 +
144 +static int keep_entry(struct commit **it, struct object_id *oid)
145 +{
146 + struct commit *commit;
147 +
148 + if (is_null_oid(oid))
149 + return 1;
150 + commit = lookup_commit_reference_gently(the_repository, oid, 1);
151 + if (!commit)
152 + return 0;
153 +
154 + /*
155 + * Make sure everything in this commit exists.
156 + *
157 + * We have walked all the objects reachable from the refs
158 + * and cache earlier. The commits reachable by this commit
159 + * must meet SEEN commits -- and then we should mark them as
160 + * SEEN as well.
161 + */
162 + if (!commit_is_complete(commit))
163 + return 0;
164 + *it = commit;
165 + return 1;
166 +}
167 +
168 +/*
169 + * Starting from commits in the cb->mark_list, mark commits that are
170 + * reachable from them. Stop the traversal at commits older than
171 + * the expire_limit and queue them back, so that the caller can call
172 + * us again to restart the traversal with longer expire_limit.
173 + */
174 +static void mark_reachable(struct expire_reflog_policy_cb *cb)
175 +{
176 + struct commit_list *pending;
177 + timestamp_t expire_limit = cb->mark_limit;
178 + struct commit_list *leftover = NULL;
179 +
180 + for (pending = cb->mark_list; pending; pending = pending->next)
181 + pending->item->object.flags &= ~REACHABLE;
182 +
183 + pending = cb->mark_list;
184 + while (pending) {
185 + struct commit_list *parent;
186 + struct commit *commit = pop_commit(&pending);
187 + if (commit->object.flags & REACHABLE)
188 + continue;
189 + if (parse_commit(commit))
190 + continue;
191 + commit->object.flags |= REACHABLE;
192 + if (commit->date < expire_limit) {
193 + commit_list_insert(commit, &leftover);
194 + continue;
195 + }
196 + commit->object.flags |= REACHABLE;
197 + parent = commit->parents;
198 + while (parent) {
199 + commit = parent->item;
200 + parent = parent->next;
201 + if (commit->object.flags & REACHABLE)
202 + continue;
203 + commit_list_insert(commit, &pending);
204 + }
205 + }
206 + cb->mark_list = leftover;
207 +}
208 +
209 +static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid)
210 +{
211 + /*
212 + * We may or may not have the commit yet - if not, look it
213 + * up using the supplied sha1.
214 + */
215 + if (!commit) {
216 + if (is_null_oid(oid))
217 + return 0;
218 +
219 + commit = lookup_commit_reference_gently(the_repository, oid,
220 + 1);
221 +
222 + /* Not a commit -- keep it */
223 + if (!commit)
224 + return 0;
225 + }
226 +
227 + /* Reachable from the current ref? Don't prune. */
228 + if (commit->object.flags & REACHABLE)
229 + return 0;
230 +
231 + if (cb->mark_list && cb->mark_limit) {
232 + cb->mark_limit = 0; /* dig down to the root */
233 + mark_reachable(cb);
234 + }
235 +
236 + return !(commit->object.flags & REACHABLE);
237 +}
238 +
239 +/*
240 + * Return true iff the specified reflog entry should be expired.
241 + */
242 +int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,
243 + const char *email, timestamp_t timestamp, int tz,
244 + const char *message, void *cb_data)
245 +{
246 + struct expire_reflog_policy_cb *cb = cb_data;
247 + struct commit *old_commit, *new_commit;
248 +
249 + if (timestamp < cb->cmd.expire_total)
250 + return 1;
251 +
252 + old_commit = new_commit = NULL;
253 + if (cb->cmd.stalefix &&
254 + (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid)))
255 + return 1;
256 +
257 + if (timestamp < cb->cmd.expire_unreachable) {
258 + switch (cb->unreachable_expire_kind) {
259 + case UE_ALWAYS:
260 + return 1;
261 + case UE_NORMAL:
262 + case UE_HEAD:
263 + if (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid))
264 + return 1;
265 + break;
266 + }
267 + }
268 +
269 + if (cb->cmd.recno && --(cb->cmd.recno) == 0)
270 + return 1;
271 +
272 + return 0;
273 +}
274 +
275 +int should_expire_reflog_ent_verbose(struct object_id *ooid,
276 + struct object_id *noid,
277 + const char *email,
278 + timestamp_t timestamp, int tz,
279 + const char *message, void *cb_data)
280 +{
281 + struct expire_reflog_policy_cb *cb = cb_data;
282 + int expire;
283 +
284 + expire = should_expire_reflog_ent(ooid, noid, email, timestamp, tz,
285 + message, cb);
286 +
287 + if (!expire)
288 + printf("keep %s", message);
289 + else if (cb->dry_run)
290 + printf("would prune %s", message);
291 + else
292 + printf("prune %s", message);
293 +
294 + return expire;
295 +}
296 +
297 +static int push_tip_to_list(const char *refname, const struct object_id *oid,
298 + int flags, void *cb_data)
299 +{
300 + struct commit_list **list = cb_data;
301 + struct commit *tip_commit;
302 + if (flags & REF_ISSYMREF)
303 + return 0;
304 + tip_commit = lookup_commit_reference_gently(the_repository, oid, 1);
305 + if (!tip_commit)
306 + return 0;
307 + commit_list_insert(tip_commit, list);
308 + return 0;
309 +}
310 +
311 +static int is_head(const char *refname)
312 +{
313 + switch (ref_type(refname)) {
314 + case REF_TYPE_OTHER_PSEUDOREF:
315 + case REF_TYPE_MAIN_PSEUDOREF:
316 + if (parse_worktree_ref(refname, NULL, NULL, &refname))
317 + BUG("not a worktree ref: %s", refname);
318 + break;
319 + default:
320 + break;
321 + }
322 + return !strcmp(refname, "HEAD");
323 +}
324 +
325 +void reflog_expiry_prepare(const char *refname,
326 + const struct object_id *oid,
327 + void *cb_data)
328 +{
329 + struct expire_reflog_policy_cb *cb = cb_data;
330 + struct commit_list *elem;
331 + struct commit *commit = NULL;
332 +
333 + if (!cb->cmd.expire_unreachable || is_head(refname)) {
334 + cb->unreachable_expire_kind = UE_HEAD;
335 + } else {
336 + commit = lookup_commit(the_repository, oid);
337 + cb->unreachable_expire_kind = commit ? UE_NORMAL : UE_ALWAYS;
338 + }
339 +
340 + if (cb->cmd.expire_unreachable <= cb->cmd.expire_total)
341 + cb->unreachable_expire_kind = UE_ALWAYS;
342 +
343 + switch (cb->unreachable_expire_kind) {
344 + case UE_ALWAYS:
345 + return;
346 + case UE_HEAD:
347 + for_each_ref(push_tip_to_list, &cb->tips);
348 + for (elem = cb->tips; elem; elem = elem->next)
349 + commit_list_insert(elem->item, &cb->mark_list);
350 + break;
351 + case UE_NORMAL:
352 + commit_list_insert(commit, &cb->mark_list);
353 + /* For reflog_expiry_cleanup() below */
354 + cb->tip_commit = commit;
355 + }
356 + cb->mark_limit = cb->cmd.expire_total;
357 + mark_reachable(cb);
358 +}
359 +
360 +void reflog_expiry_cleanup(void *cb_data)
361 +{
362 + struct expire_reflog_policy_cb *cb = cb_data;
363 + struct commit_list *elem;
364 +
365 + switch (cb->unreachable_expire_kind) {
366 + case UE_ALWAYS:
367 + return;
368 + case UE_HEAD:
369 + for (elem = cb->tips; elem; elem = elem->next)
370 + clear_commit_marks(elem->item, REACHABLE);
371 + free_commit_list(cb->tips);
372 + break;
373 + case UE_NORMAL:
374 + clear_commit_marks(cb->tip_commit, REACHABLE);
375 + break;
376 + }
377 +}
378 +
379 +int count_reflog_ent(struct object_id *ooid, struct object_id *noid,
380 + const char *email, timestamp_t timestamp, int tz,
381 + const char *message, void *cb_data)
382 +{
383 + struct cmd_reflog_expire_cb *cb = cb_data;
384 + if (!cb->expire_total || timestamp < cb->expire_total)
385 + cb->recno++;
386 + return 0;
387 +}
388 +
389 +int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose)
390 +{
391 + struct cmd_reflog_expire_cb cmd = { 0 };
392 + int status = 0;
393 + reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent;
394 + const char *spec = strstr(rev, "@{");
395 + char *ep, *ref;
396 + int recno;
397 + struct expire_reflog_policy_cb cb = {
398 + .dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN),
399 + };
400 +
401 + if (verbose)
402 + should_prune_fn = should_expire_reflog_ent_verbose;
403 +
404 + if (!spec)
405 + return error(_("not a reflog: %s"), rev);
406 +
407 + if (!dwim_log(rev, spec - rev, NULL, &ref)) {
408 + status |= error(_("no reflog for '%s'"), rev);
409 + goto cleanup;
410 + }
411 +
412 + recno = strtoul(spec + 2, &ep, 10);
413 + if (*ep == '}') {
414 + cmd.recno = -recno;
415 + for_each_reflog_ent(ref, count_reflog_ent, &cmd);
416 + } else {
417 + cmd.expire_total = approxidate(spec + 2);
418 + for_each_reflog_ent(ref, count_reflog_ent, &cmd);
419 + cmd.expire_total = 0;
420 + }
421 +
422 + cb.cmd = cmd;
423 + status |= reflog_expire(ref, flags,
424 + reflog_expiry_prepare,
425 + should_prune_fn,
426 + reflog_expiry_cleanup,
427 + &cb);
428 +
429 + cleanup:
430 + free(ref);
431 + return status;
432 +}
reflog.h new
+43
@@ -0,0 +1,43 @@
1 +#ifndef REFLOG_H
2 +#define REFLOG_H
3 +#include "refs.h"
4 +
5 +struct cmd_reflog_expire_cb {
6 + int stalefix;
7 + int explicit_expiry;
8 + timestamp_t expire_total;
9 + timestamp_t expire_unreachable;
10 + int recno;
11 +};
12 +
13 +struct expire_reflog_policy_cb {
14 + enum {
15 + UE_NORMAL,
16 + UE_ALWAYS,
17 + UE_HEAD
18 + } unreachable_expire_kind;
19 + struct commit_list *mark_list;
20 + unsigned long mark_limit;
21 + struct cmd_reflog_expire_cb cmd;
22 + struct commit *tip_commit;
23 + struct commit_list *tips;
24 + unsigned int dry_run:1;
25 +};
26 +
27 +int reflog_delete(const char *rev, enum expire_reflog_flags flags,
28 + int verbose);
29 +void reflog_expiry_cleanup(void *cb_data);
30 +void reflog_expiry_prepare(const char *refname, const struct object_id *oid,
31 + void *cb_data);
32 +int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid,
33 + const char *email, timestamp_t timestamp, int tz,
34 + const char *message, void *cb_data);
35 +int count_reflog_ent(struct object_id *ooid, struct object_id *noid,
36 + const char *email, timestamp_t timestamp, int tz,
37 + const char *message, void *cb_data);
38 +int should_expire_reflog_ent_verbose(struct object_id *ooid,
39 + struct object_id *noid,
40 + const char *email,
41 + timestamp_t timestamp, int tz,
42 + const char *message, void *cb_data);
43 +#endif /* REFLOG_H */