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