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