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