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