Coverage Report

Created: 2026-11-12 06:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/repack.c
Line
Count
Source
1
#include "git-compat-util.h"
2
#include "dir.h"
3
#include "midx.h"
4
#include "odb.h"
5
#include "packfile.h"
6
#include "path.h"
7
#include "repack.h"
8
#include "repository.h"
9
#include "run-command.h"
10
#include "tempfile.h"
11
12
void prepare_pack_objects(struct child_process *cmd,
13
        const struct pack_objects_args *args,
14
        const char *out)
15
0
{
16
0
  strvec_push(&cmd->args, "pack-objects");
17
0
  if (args->window)
18
0
    strvec_pushf(&cmd->args, "--window=%s", args->window);
19
0
  if (args->window_memory)
20
0
    strvec_pushf(&cmd->args, "--window-memory=%s", args->window_memory);
21
0
  if (args->depth)
22
0
    strvec_pushf(&cmd->args, "--depth=%s", args->depth);
23
0
  if (args->threads)
24
0
    strvec_pushf(&cmd->args, "--threads=%s", args->threads);
25
0
  if (args->max_pack_size)
26
0
    strvec_pushf(&cmd->args, "--max-pack-size=%lu", args->max_pack_size);
27
0
  if (args->no_reuse_delta)
28
0
    strvec_pushf(&cmd->args, "--no-reuse-delta");
29
0
  if (args->no_reuse_object)
30
0
    strvec_pushf(&cmd->args, "--no-reuse-object");
31
0
  if (args->name_hash_version)
32
0
    strvec_pushf(&cmd->args, "--name-hash-version=%d", args->name_hash_version);
33
0
  if (args->path_walk)
34
0
    strvec_pushf(&cmd->args, "--path-walk");
35
0
  if (args->local)
36
0
    strvec_push(&cmd->args,  "--local");
37
0
  if (args->quiet)
38
0
    strvec_push(&cmd->args,  "--quiet");
39
0
  if (args->delta_base_offset)
40
0
    strvec_push(&cmd->args,  "--delta-base-offset");
41
0
  if (!args->pack_kept_objects)
42
0
    strvec_push(&cmd->args,  "--honor-pack-keep");
43
0
  strvec_push(&cmd->args, out);
44
0
  cmd->git_cmd = 1;
45
0
  cmd->out = -1;
46
0
}
47
48
void pack_objects_args_release(struct pack_objects_args *args)
49
0
{
50
0
  free(args->window);
51
0
  free(args->window_memory);
52
0
  free(args->depth);
53
0
  free(args->threads);
54
0
  list_objects_filter_release(&args->filter_options);
55
0
}
56
57
void repack_remove_redundant_pack(struct repository *repo, const char *dir_name,
58
          const char *base_name,
59
          bool wrote_incremental_midx)
60
0
{
61
0
  struct strbuf buf = STRBUF_INIT;
62
0
  struct odb_source_files *files = odb_source_files_downcast(repo->objects->source);
63
0
  struct multi_pack_index *m = get_multi_pack_index(files->dirs->packed);
64
0
  strbuf_addf(&buf, "%s.pack", base_name);
65
0
  if (m && files->dirs->local && midx_contains_pack(m, buf.buf)) {
66
0
    clear_midx_file(repo);
67
0
    if (!wrote_incremental_midx)
68
0
      clear_incremental_midx_files(repo, NULL);
69
0
  }
70
0
  strbuf_insertf(&buf, 0, "%s/", dir_name);
71
0
  unlink_pack_path(buf.buf, 1);
72
0
  strbuf_release(&buf);
73
0
}
74
75
const char *write_pack_opts_pack_prefix(const struct write_pack_opts *opts)
76
0
{
77
0
  const char *pack_prefix;
78
0
  if (!skip_prefix(opts->packtmp, opts->packdir, &pack_prefix))
79
0
    die(_("pack prefix %s does not begin with objdir %s"),
80
0
        opts->packtmp, opts->packdir);
81
0
  if (*pack_prefix == '/')
82
0
    pack_prefix++;
83
0
  return pack_prefix;
84
0
}
85
86
bool write_pack_opts_is_local(const struct write_pack_opts *opts)
87
0
{
88
0
  return starts_with(opts->destination, opts->packdir);
89
0
}
90
91
int finish_pack_objects_cmd(const struct git_hash_algo *algop,
92
          const struct write_pack_opts *opts,
93
          struct child_process *cmd,
94
          struct string_list *names)
95
0
{
96
0
  FILE *out;
97
0
  bool local = write_pack_opts_is_local(opts);
98
0
  struct strbuf line = STRBUF_INIT;
99
100
0
  out = xfdopen(cmd->out, "r");
101
0
  while (strbuf_getline_lf(&line, out) != EOF) {
102
0
    struct string_list_item *item;
103
104
0
    if (line.len != algop->hexsz)
105
0
      die(_("repack: Expecting full hex object ID lines only "
106
0
            "from pack-objects."));
107
    /*
108
     * Avoid putting packs written outside of the repository in the
109
     * list of names.
110
     */
111
0
    if (local) {
112
0
      item = string_list_append(names, line.buf);
113
0
      item->util = generated_pack_populate(line.buf,
114
0
                   opts->packtmp);
115
0
    }
116
0
  }
117
0
  fclose(out);
118
119
0
  strbuf_release(&line);
120
121
0
  return finish_command(cmd);
122
0
}
123
124
0
#define DELETE_PACK 1
125
0
#define RETAIN_PACK 2
126
127
void existing_packs_collect(struct existing_packs *existing,
128
          const struct string_list *extra_keep)
129
0
{
130
0
  struct packed_git *p;
131
0
  struct strbuf buf = STRBUF_INIT;
132
133
0
  repo_for_each_pack(existing->repo, p) {
134
0
    size_t i;
135
0
    const char *base;
136
137
0
    if (p->multi_pack_index)
138
0
      string_list_append(&existing->midx_packs,
139
0
              pack_basename(p));
140
0
    if (!p->pack_local)
141
0
      continue;
142
143
0
    base = pack_basename(p);
144
145
0
    for (i = 0; i < extra_keep->nr; i++)
146
0
      if (!fspathcmp(base, extra_keep->items[i].string))
147
0
        break;
148
149
0
    strbuf_reset(&buf);
150
0
    strbuf_addstr(&buf, base);
151
0
    strbuf_strip_suffix(&buf, ".pack");
152
153
0
    if ((extra_keep->nr > 0 && i < extra_keep->nr) || p->pack_keep)
154
0
      string_list_append(&existing->kept_packs, buf.buf);
155
0
    else if (p->is_cruft)
156
0
      string_list_append(&existing->cruft_packs, buf.buf);
157
0
    else
158
0
      string_list_append(&existing->non_kept_packs, buf.buf);
159
0
  }
160
161
0
  existing->source = existing->repo->objects->source;
162
163
0
  string_list_sort(&existing->kept_packs);
164
0
  string_list_sort(&existing->non_kept_packs);
165
0
  string_list_sort(&existing->cruft_packs);
166
0
  string_list_sort(&existing->midx_packs);
167
0
  strbuf_release(&buf);
168
0
}
169
170
int existing_packs_has_non_kept(const struct existing_packs *existing)
171
0
{
172
0
  return existing->non_kept_packs.nr || existing->cruft_packs.nr;
173
0
}
174
175
static void existing_pack_mark_for_deletion(struct string_list_item *item)
176
0
{
177
0
  item->util = (void*)((uintptr_t)item->util | DELETE_PACK);
178
0
}
179
180
static void existing_pack_unmark_for_deletion(struct string_list_item *item)
181
0
{
182
0
  item->util = (void*)((uintptr_t)item->util & ~DELETE_PACK);
183
0
}
184
185
int existing_pack_is_marked_for_deletion(struct string_list_item *item)
186
0
{
187
0
  return (uintptr_t)item->util & DELETE_PACK;
188
0
}
189
190
static void existing_packs_mark_retained(struct string_list_item *item)
191
0
{
192
0
  item->util = (void*)((uintptr_t)item->util | RETAIN_PACK);
193
0
}
194
195
static int existing_pack_is_retained(struct string_list_item *item)
196
0
{
197
0
  return (uintptr_t)item->util & RETAIN_PACK;
198
0
}
199
200
static void existing_packs_mark_for_deletion_1(const struct git_hash_algo *algop,
201
                 struct string_list *names,
202
                 struct string_list *list)
203
0
{
204
0
  struct string_list_item *item;
205
0
  const size_t hexsz = algop->hexsz;
206
207
0
  for_each_string_list_item(item, list) {
208
0
    char *sha1;
209
0
    size_t len = strlen(item->string);
210
0
    if (len < hexsz)
211
0
      continue;
212
0
    sha1 = item->string + len - hexsz;
213
214
0
    if (existing_pack_is_retained(item)) {
215
0
      existing_pack_unmark_for_deletion(item);
216
0
    } else if (!string_list_has_string(names, sha1)) {
217
      /*
218
       * Mark this pack for deletion, which ensures
219
       * that this pack won't be included in a MIDX
220
       * (if `--write-midx` was given) and that we
221
       * will actually delete this pack (if `-d` was
222
       * given).
223
       */
224
0
      existing_pack_mark_for_deletion(item);
225
0
    }
226
0
  }
227
0
}
228
229
void existing_packs_retain_cruft(struct existing_packs *existing,
230
         struct packed_git *cruft)
231
0
{
232
0
  struct strbuf buf = STRBUF_INIT;
233
0
  struct string_list_item *item;
234
235
0
  strbuf_addstr(&buf, pack_basename(cruft));
236
0
  strbuf_strip_suffix(&buf, ".pack");
237
238
0
  item = string_list_lookup(&existing->cruft_packs, buf.buf);
239
0
  if (!item)
240
0
    BUG("could not find cruft pack '%s'", pack_basename(cruft));
241
242
0
  existing_packs_mark_retained(item);
243
0
  strbuf_release(&buf);
244
0
}
245
246
void existing_packs_mark_for_deletion(struct existing_packs *existing,
247
              struct string_list *names)
248
249
0
{
250
0
  const struct git_hash_algo *algop = existing->repo->hash_algo;
251
0
  existing_packs_mark_for_deletion_1(algop, names,
252
0
             &existing->non_kept_packs);
253
0
  existing_packs_mark_for_deletion_1(algop, names,
254
0
             &existing->cruft_packs);
255
0
}
256
257
/*
258
 * Mark every pack that is referenced by the existing MIDX chain as
259
 * retained, so that a subsequent call to
260
 * existing_packs_mark_for_deletion() will not mark them for deletion.
261
 *
262
 * This is used when writing an incremental MIDX layer on top of an
263
 * existing chain: retained layers continue to reference the same
264
 * packs on disk, so those packs must not be unlinked even if the
265
 * freshly-written pack supersedes them.
266
 */
267
void existing_packs_retain_midx_packs(struct existing_packs *existing)
268
0
{
269
0
  struct string_list_item *item;
270
0
  struct strbuf buf = STRBUF_INIT;
271
272
0
  for_each_string_list_item(item, &existing->midx_packs) {
273
0
    struct string_list_item *found;
274
275
0
    strbuf_reset(&buf);
276
0
    strbuf_addstr(&buf, item->string);
277
0
    strbuf_strip_suffix(&buf, ".pack");
278
0
    strbuf_strip_suffix(&buf, ".idx");
279
280
0
    found = string_list_lookup(&existing->non_kept_packs, buf.buf);
281
0
    if (found)
282
0
      existing_packs_mark_retained(found);
283
284
0
    found = string_list_lookup(&existing->cruft_packs, buf.buf);
285
0
    if (found)
286
0
      existing_packs_mark_retained(found);
287
0
  }
288
289
0
  strbuf_release(&buf);
290
0
}
291
292
static void remove_redundant_packs_1(struct repository *repo,
293
             struct string_list *packs,
294
             const char *packdir,
295
             bool wrote_incremental_midx)
296
0
{
297
0
  struct string_list_item *item;
298
0
  for_each_string_list_item(item, packs) {
299
0
    if (!existing_pack_is_marked_for_deletion(item))
300
0
      continue;
301
0
    repack_remove_redundant_pack(repo, packdir, item->string,
302
0
               wrote_incremental_midx);
303
0
  }
304
0
}
305
306
void existing_packs_remove_redundant(struct existing_packs *existing,
307
             const char *packdir,
308
             bool wrote_incremental_midx)
309
0
{
310
0
  remove_redundant_packs_1(existing->repo, &existing->non_kept_packs,
311
0
         packdir, wrote_incremental_midx);
312
0
  remove_redundant_packs_1(existing->repo, &existing->cruft_packs,
313
0
         packdir, wrote_incremental_midx);
314
0
}
315
316
void existing_packs_release(struct existing_packs *existing)
317
0
{
318
0
  string_list_clear(&existing->kept_packs, 0);
319
0
  string_list_clear(&existing->non_kept_packs, 0);
320
0
  string_list_clear(&existing->cruft_packs, 0);
321
0
  string_list_clear(&existing->midx_packs, 0);
322
0
}
323
324
static struct {
325
  const char *name;
326
  unsigned optional:1;
327
} exts[] = {
328
  {".pack"},
329
  {".rev", 1},
330
  {".mtimes", 1},
331
  {".bitmap", 1},
332
  {".promisor", 1},
333
  {".idx"},
334
};
335
336
struct generated_pack {
337
  struct tempfile *tempfiles[ARRAY_SIZE(exts)];
338
};
339
340
struct generated_pack *generated_pack_populate(const char *name,
341
                 const char *packtmp)
342
0
{
343
0
  struct stat statbuf;
344
0
  struct strbuf path = STRBUF_INIT;
345
0
  struct generated_pack *pack = xcalloc(1, sizeof(*pack));
346
0
  size_t i;
347
348
0
  for (i = 0; i < ARRAY_SIZE(exts); i++) {
349
0
    strbuf_reset(&path);
350
0
    strbuf_addf(&path, "%s-%s%s", packtmp, name, exts[i].name);
351
352
0
    if (stat(path.buf, &statbuf))
353
0
      continue;
354
355
0
    pack->tempfiles[i] = register_tempfile(path.buf);
356
0
  }
357
358
0
  strbuf_release(&path);
359
0
  return pack;
360
0
}
361
362
int generated_pack_has_ext(const struct generated_pack *pack, const char *ext)
363
0
{
364
0
  size_t i;
365
0
  for (i = 0; i < ARRAY_SIZE(exts); i++) {
366
0
    if (strcmp(exts[i].name, ext))
367
0
      continue;
368
0
    return !!pack->tempfiles[i];
369
0
  }
370
0
  BUG("unknown pack extension: '%s'", ext);
371
0
}
372
373
void generated_pack_install(struct generated_pack *pack, const char *name,
374
          const char *packdir, const char *packtmp)
375
0
{
376
0
  size_t ext;
377
0
  for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
378
0
    char *fname;
379
380
0
    fname = mkpathdup("%s/pack-%s%s", packdir, name,
381
0
          exts[ext].name);
382
383
0
    if (pack->tempfiles[ext]) {
384
0
      const char *fname_old = get_tempfile_path(pack->tempfiles[ext]);
385
0
      struct stat statbuffer;
386
387
0
      if (!stat(fname_old, &statbuffer)) {
388
0
        statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
389
0
        chmod(fname_old, statbuffer.st_mode);
390
0
      }
391
392
0
      if (rename_tempfile(&pack->tempfiles[ext], fname))
393
0
        die_errno(_("renaming pack to '%s' failed"),
394
0
            fname);
395
0
    } else if (!exts[ext].optional)
396
0
      die(_("pack-objects did not write a '%s' file for pack %s-%s"),
397
0
          exts[ext].name, packtmp, name);
398
0
    else if (unlink(fname) < 0 && errno != ENOENT)
399
0
      die_errno(_("could not unlink: %s"), fname);
400
401
0
    free(fname);
402
0
  }
403
0
}