Coverage Report

Created: 2026-03-21 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/tmp-objdir.c
Line
Count
Source
1
#include "git-compat-util.h"
2
#include "tmp-objdir.h"
3
#include "abspath.h"
4
#include "chdir-notify.h"
5
#include "dir.h"
6
#include "environment.h"
7
#include "object-file.h"
8
#include "path.h"
9
#include "string-list.h"
10
#include "strbuf.h"
11
#include "strvec.h"
12
#include "quote.h"
13
#include "odb.h"
14
#include "repository.h"
15
16
struct tmp_objdir {
17
  struct repository *repo;
18
  struct strbuf path;
19
  struct strvec env;
20
  struct odb_source *prev_source;
21
  int will_destroy;
22
};
23
24
/*
25
 * Allow only one tmp_objdir at a time in a running process, which simplifies
26
 * our atexit cleanup routines.  It's doubtful callers will ever need
27
 * more than one, and we can expand later if so.  You can have many such
28
 * tmp_objdirs simultaneously in many processes, of course.
29
 */
30
static struct tmp_objdir *the_tmp_objdir;
31
32
static void tmp_objdir_free(struct tmp_objdir *t)
33
0
{
34
0
  strbuf_release(&t->path);
35
0
  strvec_clear(&t->env);
36
0
  free(t);
37
0
}
38
39
static void tmp_objdir_reparent(const char *name UNUSED,
40
        const char *old_cwd,
41
        const char *new_cwd,
42
        void *cb_data)
43
0
{
44
0
  struct tmp_objdir *t = cb_data;
45
0
  char *path;
46
47
0
  path = reparent_relative_path(old_cwd, new_cwd,
48
0
              t->path.buf);
49
0
  strbuf_reset(&t->path);
50
0
  strbuf_addstr(&t->path, path);
51
0
  free(path);
52
0
}
53
54
int tmp_objdir_destroy(struct tmp_objdir *t)
55
0
{
56
0
  int err;
57
58
0
  if (!t)
59
0
    return 0;
60
61
0
  if (t == the_tmp_objdir)
62
0
    the_tmp_objdir = NULL;
63
64
0
  if (t->prev_source)
65
0
    odb_restore_primary_source(t->repo->objects, t->prev_source, t->path.buf);
66
67
0
  err = remove_dir_recursively(&t->path, 0);
68
69
0
  chdir_notify_unregister(NULL, tmp_objdir_reparent, t);
70
0
  tmp_objdir_free(t);
71
72
0
  return err;
73
0
}
74
75
static void remove_tmp_objdir(void)
76
0
{
77
0
  tmp_objdir_destroy(the_tmp_objdir);
78
0
}
79
80
void tmp_objdir_discard_objects(struct tmp_objdir *t)
81
0
{
82
0
  remove_dir_recursively(&t->path, REMOVE_DIR_KEEP_TOPLEVEL);
83
0
}
84
85
/*
86
 * These env_* functions are for setting up the child environment; the
87
 * "replace" variant overrides the value of any existing variable with that
88
 * "key". The "append" variant puts our new value at the end of a list,
89
 * separated by PATH_SEP (which is what separate values in
90
 * GIT_ALTERNATE_OBJECT_DIRECTORIES).
91
 */
92
static void env_append(struct strvec *env, const char *key, const char *val)
93
0
{
94
0
  struct strbuf quoted = STRBUF_INIT;
95
0
  const char *old;
96
97
  /*
98
   * Avoid quoting if it's not necessary, for maximum compatibility
99
   * with older parsers which don't understand the quoting.
100
   */
101
0
  if (*val == '"' || strchr(val, PATH_SEP)) {
102
0
    strbuf_addch(&quoted, '"');
103
0
    quote_c_style(val, &quoted, NULL, 1);
104
0
    strbuf_addch(&quoted, '"');
105
0
    val = quoted.buf;
106
0
  }
107
108
0
  old = getenv(key);
109
0
  if (!old)
110
0
    strvec_pushf(env, "%s=%s", key, val);
111
0
  else
112
0
    strvec_pushf(env, "%s=%s%c%s", key, old, PATH_SEP, val);
113
114
0
  strbuf_release(&quoted);
115
0
}
116
117
static void env_replace(struct strvec *env, const char *key, const char *val)
118
0
{
119
0
  strvec_pushf(env, "%s=%s", key, val);
120
0
}
121
122
static int setup_tmp_objdir(const char *root)
123
0
{
124
0
  char *path;
125
0
  int ret = 0;
126
127
0
  path = xstrfmt("%s/pack", root);
128
0
  ret = mkdir(path, 0777);
129
0
  free(path);
130
131
0
  return ret;
132
0
}
133
134
struct tmp_objdir *tmp_objdir_create(struct repository *r,
135
             const char *prefix)
136
0
{
137
0
  static int installed_handlers;
138
0
  struct tmp_objdir *t;
139
140
0
  if (the_tmp_objdir)
141
0
    BUG("only one tmp_objdir can be used at a time");
142
143
0
  t = xcalloc(1, sizeof(*t));
144
0
  t->repo = r;
145
0
  strbuf_init(&t->path, 0);
146
0
  strvec_init(&t->env);
147
148
  /*
149
   * Use a string starting with tmp_ so that the builtin/prune.c code
150
   * can recognize any stale objdirs left behind by a crash and delete
151
   * them.
152
   */
153
0
  strbuf_addf(&t->path, "%s/tmp_objdir-%s-XXXXXX",
154
0
        repo_get_object_directory(r), prefix);
155
156
0
  if (!is_absolute_path(t->path.buf))
157
0
    chdir_notify_register(NULL, tmp_objdir_reparent, t);
158
159
0
  if (!mkdtemp(t->path.buf)) {
160
    /* free, not destroy, as we never touched the filesystem */
161
0
    tmp_objdir_free(t);
162
0
    return NULL;
163
0
  }
164
165
0
  the_tmp_objdir = t;
166
0
  if (!installed_handlers) {
167
0
    atexit(remove_tmp_objdir);
168
0
    installed_handlers++;
169
0
  }
170
171
0
  if (setup_tmp_objdir(t->path.buf)) {
172
0
    tmp_objdir_destroy(t);
173
0
    return NULL;
174
0
  }
175
176
0
  env_append(&t->env, ALTERNATE_DB_ENVIRONMENT,
177
0
       absolute_path(repo_get_object_directory(r)));
178
0
  env_replace(&t->env, DB_ENVIRONMENT, absolute_path(t->path.buf));
179
0
  env_replace(&t->env, GIT_QUARANTINE_ENVIRONMENT,
180
0
        absolute_path(t->path.buf));
181
182
0
  return t;
183
0
}
184
185
/*
186
 * Make sure we copy packfiles and their associated metafiles in the correct
187
 * order. All of these ends_with checks are slightly expensive to do in
188
 * the midst of a sorting routine, but in practice it shouldn't matter.
189
 * We will have a relatively small number of packfiles to order, and loose
190
 * objects exit early in the first line.
191
 */
192
static int pack_copy_priority(const char *name)
193
0
{
194
0
  if (!starts_with(name, "pack"))
195
0
    return 0;
196
0
  if (ends_with(name, ".keep"))
197
0
    return 1;
198
0
  if (ends_with(name, ".pack"))
199
0
    return 2;
200
0
  if (ends_with(name, ".rev"))
201
0
    return 3;
202
0
  if (ends_with(name, ".idx"))
203
0
    return 4;
204
0
  return 5;
205
0
}
206
207
static int pack_copy_cmp(const char *a, const char *b)
208
0
{
209
0
  return pack_copy_priority(a) - pack_copy_priority(b);
210
0
}
211
212
static int read_dir_paths(struct string_list *out, const char *path)
213
0
{
214
0
  DIR *dh;
215
0
  struct dirent *de;
216
217
0
  dh = opendir(path);
218
0
  if (!dh)
219
0
    return -1;
220
221
0
  while ((de = readdir(dh)))
222
0
    if (de->d_name[0] != '.')
223
0
      string_list_append(out, de->d_name);
224
225
0
  closedir(dh);
226
0
  return 0;
227
0
}
228
229
static int migrate_paths(struct tmp_objdir *t,
230
       struct strbuf *src, struct strbuf *dst,
231
       enum finalize_object_file_flags flags);
232
233
static int migrate_one(struct tmp_objdir *t,
234
           struct strbuf *src, struct strbuf *dst,
235
           enum finalize_object_file_flags flags)
236
0
{
237
0
  struct stat st;
238
239
0
  if (stat(src->buf, &st) < 0)
240
0
    return -1;
241
0
  if (S_ISDIR(st.st_mode)) {
242
0
    if (!mkdir(dst->buf, 0777)) {
243
0
      if (adjust_shared_perm(t->repo, dst->buf))
244
0
        return -1;
245
0
    } else if (errno != EEXIST)
246
0
      return -1;
247
0
    return migrate_paths(t, src, dst, flags);
248
0
  }
249
0
  return finalize_object_file_flags(t->repo, src->buf, dst->buf, flags);
250
0
}
251
252
static int is_loose_object_shard(const char *name)
253
0
{
254
0
  return strlen(name) == 2 && isxdigit(name[0]) && isxdigit(name[1]);
255
0
}
256
257
static int migrate_paths(struct tmp_objdir *t,
258
       struct strbuf *src, struct strbuf *dst,
259
       enum finalize_object_file_flags flags)
260
0
{
261
0
  size_t src_len = src->len, dst_len = dst->len;
262
0
  struct string_list paths = STRING_LIST_INIT_DUP;
263
0
  int ret = 0;
264
265
0
  if (read_dir_paths(&paths, src->buf) < 0)
266
0
    return -1;
267
0
  paths.cmp = pack_copy_cmp;
268
0
  string_list_sort(&paths);
269
270
0
  for (size_t i = 0; i < paths.nr; i++) {
271
0
    const char *name = paths.items[i].string;
272
0
    enum finalize_object_file_flags flags_copy = flags;
273
274
0
    strbuf_addf(src, "/%s", name);
275
0
    strbuf_addf(dst, "/%s", name);
276
277
0
    if (is_loose_object_shard(name))
278
0
      flags_copy |= FOF_SKIP_COLLISION_CHECK;
279
280
0
    ret |= migrate_one(t, src, dst, flags_copy);
281
282
0
    strbuf_setlen(src, src_len);
283
0
    strbuf_setlen(dst, dst_len);
284
0
  }
285
286
0
  string_list_clear(&paths, 0);
287
0
  return ret;
288
0
}
289
290
int tmp_objdir_migrate(struct tmp_objdir *t)
291
0
{
292
0
  struct strbuf src = STRBUF_INIT, dst = STRBUF_INIT;
293
0
  int ret;
294
295
0
  if (!t)
296
0
    return 0;
297
298
0
  if (t->prev_source) {
299
0
    if (t->repo->objects->sources->will_destroy)
300
0
      BUG("migrating an ODB that was marked for destruction");
301
0
    odb_restore_primary_source(t->repo->objects, t->prev_source, t->path.buf);
302
0
    t->prev_source = NULL;
303
0
  }
304
305
0
  strbuf_addbuf(&src, &t->path);
306
0
  strbuf_addstr(&dst, repo_get_object_directory(t->repo));
307
308
0
  ret = migrate_paths(t, &src, &dst, 0);
309
310
0
  strbuf_release(&src);
311
0
  strbuf_release(&dst);
312
313
0
  tmp_objdir_destroy(t);
314
0
  return ret;
315
0
}
316
317
const char **tmp_objdir_env(const struct tmp_objdir *t)
318
0
{
319
0
  if (!t)
320
0
    return NULL;
321
0
  return t->env.v;
322
0
}
323
324
void tmp_objdir_add_as_alternate(const struct tmp_objdir *t)
325
0
{
326
0
  odb_add_to_alternates_memory(t->repo->objects, t->path.buf);
327
0
}
328
329
void tmp_objdir_replace_primary_odb(struct tmp_objdir *t, int will_destroy)
330
0
{
331
0
  if (t->prev_source)
332
0
    BUG("the primary object database is already replaced");
333
0
  t->prev_source = odb_set_temporary_primary_source(t->repo->objects,
334
0
                t->path.buf, will_destroy);
335
0
  t->will_destroy = will_destroy;
336
0
}