Coverage Report

Created: 2025-12-31 07:01

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