Coverage Report

Created: 2024-09-08 06:23

/src/git/reflog.c
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
}