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 */