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