]>
Commit | Line | Data |
---|---|---|
d00d5464 ET |
1 | /* |
2 | * Copyright (C) the libgit2 contributors. All rights reserved. | |
3 | * | |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
7 | ||
8 | #include "refs.h" | |
9 | #include "hash.h" | |
10 | #include "repository.h" | |
11 | #include "fileops.h" | |
12 | #include "pack.h" | |
13 | #include "reflog.h" | |
d00d5464 ET |
14 | #include "refdb.h" |
15 | #include "refdb_fs.h" | |
4def7035 | 16 | #include "iterator.h" |
d00d5464 ET |
17 | |
18 | #include <git2/tag.h> | |
19 | #include <git2/object.h> | |
20 | #include <git2/refdb.h> | |
4dcd8780 | 21 | #include <git2/sys/refdb_backend.h> |
21ca0451 | 22 | #include <git2/sys/refs.h> |
d00d5464 ET |
23 | |
24 | GIT__USE_STRMAP; | |
25 | ||
26 | #define DEFAULT_NESTING_LEVEL 5 | |
27 | #define MAX_NESTING_LEVEL 10 | |
28 | ||
29 | enum { | |
f69db390 VM |
30 | PACKREF_HAS_PEEL = 1, |
31 | PACKREF_WAS_LOOSE = 2, | |
2638a03a VM |
32 | PACKREF_CANNOT_PEEL = 4, |
33 | PACKREF_SHADOWED = 8, | |
f69db390 VM |
34 | }; |
35 | ||
36 | enum { | |
37 | PEELING_NONE = 0, | |
38 | PEELING_STANDARD, | |
39 | PEELING_FULL | |
d00d5464 ET |
40 | }; |
41 | ||
42 | struct packref { | |
43 | git_oid oid; | |
44 | git_oid peel; | |
45 | char flags; | |
46 | char name[GIT_FLEX_ARRAY]; | |
47 | }; | |
48 | ||
49 | typedef struct refdb_fs_backend { | |
50 | git_refdb_backend parent; | |
51 | ||
52 | git_repository *repo; | |
bade5194 | 53 | char *path; |
d00d5464 ET |
54 | |
55 | git_refcache refcache; | |
f69db390 | 56 | int peeling_mode; |
d00d5464 ET |
57 | } refdb_fs_backend; |
58 | ||
59 | static int reference_read( | |
60 | git_buf *file_content, | |
61 | time_t *mtime, | |
62 | const char *repo_path, | |
63 | const char *ref_name, | |
64 | int *updated) | |
65 | { | |
66 | git_buf path = GIT_BUF_INIT; | |
67 | int result; | |
68 | ||
69 | assert(file_content && repo_path && ref_name); | |
70 | ||
71 | /* Determine the full path of the file */ | |
72 | if (git_buf_joinpath(&path, repo_path, ref_name) < 0) | |
73 | return -1; | |
4dcd8780 | 74 | |
d00d5464 ET |
75 | result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, NULL, updated); |
76 | git_buf_free(&path); | |
77 | ||
78 | return result; | |
79 | } | |
80 | ||
81 | static int packed_parse_oid( | |
82 | struct packref **ref_out, | |
83 | const char **buffer_out, | |
84 | const char *buffer_end) | |
85 | { | |
86 | struct packref *ref = NULL; | |
87 | ||
88 | const char *buffer = *buffer_out; | |
89 | const char *refname_begin, *refname_end; | |
90 | ||
91 | size_t refname_len; | |
92 | git_oid id; | |
93 | ||
94 | refname_begin = (buffer + GIT_OID_HEXSZ + 1); | |
95 | if (refname_begin >= buffer_end || refname_begin[-1] != ' ') | |
96 | goto corrupt; | |
97 | ||
98 | /* Is this a valid object id? */ | |
99 | if (git_oid_fromstr(&id, buffer) < 0) | |
100 | goto corrupt; | |
101 | ||
102 | refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); | |
103 | if (refname_end == NULL) | |
104 | refname_end = buffer_end; | |
105 | ||
106 | if (refname_end[-1] == '\r') | |
107 | refname_end--; | |
108 | ||
109 | refname_len = refname_end - refname_begin; | |
110 | ||
617bb175 | 111 | ref = git__calloc(1, sizeof(struct packref) + refname_len + 1); |
d00d5464 ET |
112 | GITERR_CHECK_ALLOC(ref); |
113 | ||
114 | memcpy(ref->name, refname_begin, refname_len); | |
115 | ref->name[refname_len] = 0; | |
116 | ||
117 | git_oid_cpy(&ref->oid, &id); | |
118 | ||
d00d5464 ET |
119 | *ref_out = ref; |
120 | *buffer_out = refname_end + 1; | |
d00d5464 ET |
121 | return 0; |
122 | ||
123 | corrupt: | |
124 | git__free(ref); | |
125 | giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); | |
126 | return -1; | |
127 | } | |
128 | ||
129 | static int packed_parse_peel( | |
130 | struct packref *tag_ref, | |
131 | const char **buffer_out, | |
132 | const char *buffer_end) | |
133 | { | |
134 | const char *buffer = *buffer_out + 1; | |
135 | ||
136 | assert(buffer[-1] == '^'); | |
137 | ||
138 | /* Ensure it's not the first entry of the file */ | |
139 | if (tag_ref == NULL) | |
140 | goto corrupt; | |
141 | ||
d00d5464 ET |
142 | if (buffer + GIT_OID_HEXSZ > buffer_end) |
143 | goto corrupt; | |
144 | ||
145 | /* Is this a valid object id? */ | |
146 | if (git_oid_fromstr(&tag_ref->peel, buffer) < 0) | |
147 | goto corrupt; | |
148 | ||
149 | buffer = buffer + GIT_OID_HEXSZ; | |
150 | if (*buffer == '\r') | |
151 | buffer++; | |
152 | ||
153 | if (buffer != buffer_end) { | |
154 | if (*buffer == '\n') | |
155 | buffer++; | |
156 | else | |
157 | goto corrupt; | |
158 | } | |
159 | ||
f69db390 | 160 | tag_ref->flags |= PACKREF_HAS_PEEL; |
d00d5464 ET |
161 | *buffer_out = buffer; |
162 | return 0; | |
163 | ||
164 | corrupt: | |
165 | giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); | |
166 | return -1; | |
167 | } | |
168 | ||
169 | static int packed_load(refdb_fs_backend *backend) | |
170 | { | |
171 | int result, updated; | |
172 | git_buf packfile = GIT_BUF_INIT; | |
173 | const char *buffer_start, *buffer_end; | |
174 | git_refcache *ref_cache = &backend->refcache; | |
175 | ||
176 | /* First we make sure we have allocated the hash table */ | |
177 | if (ref_cache->packfile == NULL) { | |
178 | ref_cache->packfile = git_strmap_alloc(); | |
179 | GITERR_CHECK_ALLOC(ref_cache->packfile); | |
180 | } | |
4dcd8780 | 181 | |
69a3c766 CMN |
182 | if (backend->path == NULL) |
183 | return 0; | |
184 | ||
d00d5464 ET |
185 | result = reference_read(&packfile, &ref_cache->packfile_time, |
186 | backend->path, GIT_PACKEDREFS_FILE, &updated); | |
187 | ||
188 | /* | |
189 | * If we couldn't find the file, we need to clear the table and | |
190 | * return. On any other error, we return that error. If everything | |
191 | * went fine and the file wasn't updated, then there's nothing new | |
192 | * for us here, so just return. Anything else means we need to | |
193 | * refresh the packed refs. | |
194 | */ | |
195 | if (result == GIT_ENOTFOUND) { | |
196 | git_strmap_clear(ref_cache->packfile); | |
197 | return 0; | |
198 | } | |
199 | ||
200 | if (result < 0) | |
201 | return -1; | |
4dcd8780 | 202 | |
d00d5464 ET |
203 | if (!updated) |
204 | return 0; | |
205 | ||
206 | /* | |
207 | * At this point, we want to refresh the packed refs. We already | |
208 | * have the contents in our buffer. | |
209 | */ | |
210 | git_strmap_clear(ref_cache->packfile); | |
211 | ||
212 | buffer_start = (const char *)packfile.ptr; | |
213 | buffer_end = (const char *)(buffer_start) + packfile.size; | |
214 | ||
f69db390 VM |
215 | backend->peeling_mode = PEELING_NONE; |
216 | ||
217 | if (buffer_start[0] == '#') { | |
1fed6b07 | 218 | static const char *traits_header = "# pack-refs with: "; |
f69db390 VM |
219 | |
220 | if (git__prefixcmp(buffer_start, traits_header) == 0) { | |
822645f6 VM |
221 | char *traits = (char *)buffer_start + strlen(traits_header); |
222 | char *traits_end = strchr(traits, '\n'); | |
223 | ||
224 | if (traits_end == NULL) | |
225 | goto parse_failed; | |
226 | ||
227 | *traits_end = '\0'; | |
f69db390 | 228 | |
1022db2b | 229 | if (strstr(traits, " fully-peeled ") != NULL) { |
f69db390 | 230 | backend->peeling_mode = PEELING_FULL; |
1022db2b | 231 | } else if (strstr(traits, " peeled ") != NULL) { |
f69db390 VM |
232 | backend->peeling_mode = PEELING_STANDARD; |
233 | } | |
234 | ||
235 | buffer_start = traits_end + 1; | |
236 | } | |
237 | } | |
238 | ||
d00d5464 ET |
239 | while (buffer_start < buffer_end && buffer_start[0] == '#') { |
240 | buffer_start = strchr(buffer_start, '\n'); | |
241 | if (buffer_start == NULL) | |
242 | goto parse_failed; | |
243 | ||
244 | buffer_start++; | |
245 | } | |
246 | ||
247 | while (buffer_start < buffer_end) { | |
248 | int err; | |
249 | struct packref *ref = NULL; | |
250 | ||
251 | if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0) | |
252 | goto parse_failed; | |
253 | ||
254 | if (buffer_start[0] == '^') { | |
255 | if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0) | |
256 | goto parse_failed; | |
a591ed3e | 257 | } else if (backend->peeling_mode == PEELING_FULL || |
0cb16fe9 L |
258 | (backend->peeling_mode == PEELING_STANDARD && |
259 | git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0)) { | |
f69db390 | 260 | ref->flags |= PACKREF_CANNOT_PEEL; |
d00d5464 ET |
261 | } |
262 | ||
263 | git_strmap_insert(ref_cache->packfile, ref->name, ref, err); | |
264 | if (err < 0) | |
265 | goto parse_failed; | |
266 | } | |
267 | ||
268 | git_buf_free(&packfile); | |
269 | return 0; | |
270 | ||
271 | parse_failed: | |
272 | git_strmap_free(ref_cache->packfile); | |
273 | ref_cache->packfile = NULL; | |
274 | git_buf_free(&packfile); | |
275 | return -1; | |
276 | } | |
277 | ||
a5de9044 | 278 | static int loose_parse_oid(git_oid *oid, const char *filename, git_buf *file_content) |
d00d5464 ET |
279 | { |
280 | size_t len; | |
281 | const char *str; | |
282 | ||
283 | len = git_buf_len(file_content); | |
284 | if (len < GIT_OID_HEXSZ) | |
285 | goto corrupted; | |
286 | ||
287 | /* str is guranteed to be zero-terminated */ | |
288 | str = git_buf_cstr(file_content); | |
289 | ||
290 | /* we need to get 40 OID characters from the file */ | |
291 | if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0) | |
292 | goto corrupted; | |
293 | ||
294 | /* If the file is longer than 40 chars, the 41st must be a space */ | |
295 | str += GIT_OID_HEXSZ; | |
296 | if (*str == '\0' || git__isspace(*str)) | |
297 | return 0; | |
298 | ||
299 | corrupted: | |
a5de9044 | 300 | giterr_set(GITERR_REFERENCE, "Corrupted loose reference file: %s", filename); |
d00d5464 ET |
301 | return -1; |
302 | } | |
303 | ||
304 | static int loose_lookup_to_packfile( | |
305 | struct packref **ref_out, | |
306 | refdb_fs_backend *backend, | |
307 | const char *name) | |
308 | { | |
309 | git_buf ref_file = GIT_BUF_INIT; | |
310 | struct packref *ref = NULL; | |
311 | size_t name_len; | |
312 | ||
313 | *ref_out = NULL; | |
314 | ||
315 | if (reference_read(&ref_file, NULL, backend->path, name, NULL) < 0) | |
316 | return -1; | |
317 | ||
318 | git_buf_rtrim(&ref_file); | |
319 | ||
320 | name_len = strlen(name); | |
617bb175 | 321 | ref = git__calloc(1, sizeof(struct packref) + name_len + 1); |
d00d5464 ET |
322 | GITERR_CHECK_ALLOC(ref); |
323 | ||
324 | memcpy(ref->name, name, name_len); | |
325 | ref->name[name_len] = 0; | |
326 | ||
a5de9044 | 327 | if (loose_parse_oid(&ref->oid, name, &ref_file) < 0) { |
d00d5464 ET |
328 | git_buf_free(&ref_file); |
329 | git__free(ref); | |
330 | return -1; | |
331 | } | |
332 | ||
f69db390 | 333 | ref->flags = PACKREF_WAS_LOOSE; |
d00d5464 ET |
334 | |
335 | *ref_out = ref; | |
336 | git_buf_free(&ref_file); | |
337 | return 0; | |
338 | } | |
339 | ||
340 | ||
341 | static int _dirent_loose_load(void *data, git_buf *full_path) | |
342 | { | |
343 | refdb_fs_backend *backend = (refdb_fs_backend *)data; | |
344 | void *old_ref = NULL; | |
345 | struct packref *ref; | |
346 | const char *file_path; | |
347 | int err; | |
348 | ||
349 | if (git_path_isdir(full_path->ptr) == true) | |
350 | return git_path_direach(full_path, _dirent_loose_load, backend); | |
351 | ||
352 | file_path = full_path->ptr + strlen(backend->path); | |
353 | ||
354 | if (loose_lookup_to_packfile(&ref, backend, file_path) < 0) | |
355 | return -1; | |
356 | ||
357 | git_strmap_insert2( | |
358 | backend->refcache.packfile, ref->name, ref, old_ref, err); | |
359 | if (err < 0) { | |
360 | git__free(ref); | |
361 | return -1; | |
362 | } | |
363 | ||
364 | git__free(old_ref); | |
365 | return 0; | |
366 | } | |
367 | ||
368 | /* | |
369 | * Load all the loose references from the repository | |
370 | * into the in-memory Packfile, and build a vector with | |
371 | * all the references so it can be written back to | |
372 | * disk. | |
373 | */ | |
374 | static int packed_loadloose(refdb_fs_backend *backend) | |
375 | { | |
376 | git_buf refs_path = GIT_BUF_INIT; | |
377 | int result; | |
378 | ||
379 | /* the packfile must have been previously loaded! */ | |
380 | assert(backend->refcache.packfile); | |
381 | ||
382 | if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0) | |
383 | return -1; | |
384 | ||
385 | /* | |
386 | * Load all the loose files from disk into the Packfile table. | |
387 | * This will overwrite any old packed entries with their | |
388 | * updated loose versions | |
389 | */ | |
390 | result = git_path_direach(&refs_path, _dirent_loose_load, backend); | |
391 | git_buf_free(&refs_path); | |
392 | ||
393 | return result; | |
394 | } | |
395 | ||
396 | static int refdb_fs_backend__exists( | |
397 | int *exists, | |
398 | git_refdb_backend *_backend, | |
399 | const char *ref_name) | |
400 | { | |
401 | refdb_fs_backend *backend; | |
402 | git_buf ref_path = GIT_BUF_INIT; | |
403 | ||
404 | assert(_backend); | |
405 | backend = (refdb_fs_backend *)_backend; | |
406 | ||
407 | if (packed_load(backend) < 0) | |
408 | return -1; | |
409 | ||
410 | if (git_buf_joinpath(&ref_path, backend->path, ref_name) < 0) | |
411 | return -1; | |
412 | ||
413 | if (git_path_isfile(ref_path.ptr) == true || | |
414 | git_strmap_exists(backend->refcache.packfile, ref_path.ptr)) | |
415 | *exists = 1; | |
416 | else | |
417 | *exists = 0; | |
418 | ||
419 | git_buf_free(&ref_path); | |
420 | return 0; | |
421 | } | |
422 | ||
423 | static const char *loose_parse_symbolic(git_buf *file_content) | |
424 | { | |
425 | const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); | |
426 | const char *refname_start; | |
427 | ||
428 | refname_start = (const char *)file_content->ptr; | |
429 | ||
430 | if (git_buf_len(file_content) < header_len + 1) { | |
431 | giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); | |
432 | return NULL; | |
433 | } | |
434 | ||
435 | /* | |
436 | * Assume we have already checked for the header | |
437 | * before calling this function | |
438 | */ | |
439 | refname_start += header_len; | |
440 | ||
441 | return refname_start; | |
442 | } | |
443 | ||
444 | static int loose_lookup( | |
445 | git_reference **out, | |
446 | refdb_fs_backend *backend, | |
447 | const char *ref_name) | |
448 | { | |
449 | const char *target; | |
450 | git_oid oid; | |
451 | git_buf ref_file = GIT_BUF_INIT; | |
452 | int error = 0; | |
453 | ||
454 | error = reference_read(&ref_file, NULL, backend->path, ref_name, NULL); | |
455 | ||
456 | if (error < 0) | |
457 | goto done; | |
458 | ||
459 | if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { | |
460 | git_buf_rtrim(&ref_file); | |
461 | ||
462 | if ((target = loose_parse_symbolic(&ref_file)) == NULL) { | |
463 | error = -1; | |
464 | goto done; | |
465 | } | |
466 | ||
4e4eab52 | 467 | *out = git_reference__alloc_symbolic(ref_name, target); |
d00d5464 | 468 | } else { |
a5de9044 | 469 | if ((error = loose_parse_oid(&oid, ref_name, &ref_file)) < 0) |
d00d5464 | 470 | goto done; |
4dcd8780 | 471 | |
4e4eab52 | 472 | *out = git_reference__alloc(ref_name, &oid, NULL); |
d00d5464 ET |
473 | } |
474 | ||
475 | if (*out == NULL) | |
476 | error = -1; | |
477 | ||
478 | done: | |
479 | git_buf_free(&ref_file); | |
480 | return error; | |
481 | } | |
482 | ||
483 | static int packed_map_entry( | |
484 | struct packref **entry, | |
485 | khiter_t *pos, | |
486 | refdb_fs_backend *backend, | |
487 | const char *ref_name) | |
488 | { | |
489 | git_strmap *packfile_refs; | |
490 | ||
491 | if (packed_load(backend) < 0) | |
492 | return -1; | |
4dcd8780 | 493 | |
d00d5464 ET |
494 | /* Look up on the packfile */ |
495 | packfile_refs = backend->refcache.packfile; | |
496 | ||
497 | *pos = git_strmap_lookup_index(packfile_refs, ref_name); | |
4dcd8780 | 498 | |
d00d5464 ET |
499 | if (!git_strmap_valid_index(packfile_refs, *pos)) { |
500 | giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref_name); | |
501 | return GIT_ENOTFOUND; | |
502 | } | |
503 | ||
504 | *entry = git_strmap_value_at(packfile_refs, *pos); | |
4dcd8780 | 505 | |
d00d5464 ET |
506 | return 0; |
507 | } | |
508 | ||
509 | static int packed_lookup( | |
510 | git_reference **out, | |
511 | refdb_fs_backend *backend, | |
512 | const char *ref_name) | |
513 | { | |
514 | struct packref *entry; | |
515 | khiter_t pos; | |
516 | int error = 0; | |
4dcd8780 | 517 | |
d00d5464 ET |
518 | if ((error = packed_map_entry(&entry, &pos, backend, ref_name)) < 0) |
519 | return error; | |
520 | ||
4e4eab52 | 521 | if ((*out = git_reference__alloc(ref_name, |
3be933b1 | 522 | &entry->oid, &entry->peel)) == NULL) |
d00d5464 | 523 | return -1; |
4dcd8780 | 524 | |
d00d5464 ET |
525 | return 0; |
526 | } | |
527 | ||
528 | static int refdb_fs_backend__lookup( | |
529 | git_reference **out, | |
530 | git_refdb_backend *_backend, | |
531 | const char *ref_name) | |
532 | { | |
533 | refdb_fs_backend *backend; | |
534 | int result; | |
535 | ||
536 | assert(_backend); | |
537 | ||
538 | backend = (refdb_fs_backend *)_backend; | |
539 | ||
540 | if ((result = loose_lookup(out, backend, ref_name)) == 0) | |
541 | return 0; | |
542 | ||
543 | /* only try to lookup this reference on the packfile if it | |
544 | * wasn't found on the loose refs; not if there was a critical error */ | |
545 | if (result == GIT_ENOTFOUND) { | |
546 | giterr_clear(); | |
547 | result = packed_lookup(out, backend, ref_name); | |
548 | } | |
549 | ||
550 | return result; | |
551 | } | |
552 | ||
4def7035 CMN |
553 | typedef struct { |
554 | git_reference_iterator parent; | |
2638a03a | 555 | |
ec24e542 | 556 | char *glob; |
2638a03a VM |
557 | git_vector loose; |
558 | unsigned int loose_pos; | |
559 | khiter_t packed_pos; | |
4def7035 CMN |
560 | } refdb_fs_iter; |
561 | ||
4def7035 | 562 | static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) |
d00d5464 | 563 | { |
4def7035 | 564 | refdb_fs_iter *iter = (refdb_fs_iter *) _iter; |
2638a03a VM |
565 | char *loose_path; |
566 | size_t i; | |
d00d5464 | 567 | |
2638a03a VM |
568 | git_vector_foreach(&iter->loose, i, loose_path) { |
569 | free(loose_path); | |
570 | } | |
571 | ||
572 | git_vector_free(&iter->loose); | |
ec24e542 VM |
573 | |
574 | git__free(iter->glob); | |
4def7035 CMN |
575 | git__free(iter); |
576 | } | |
d00d5464 | 577 | |
ec24e542 | 578 | static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) |
4def7035 | 579 | { |
2638a03a | 580 | git_strmap *packfile = backend->refcache.packfile; |
2638a03a VM |
581 | git_buf path = GIT_BUF_INIT; |
582 | git_iterator *fsit; | |
583 | const git_index_entry *entry = NULL; | |
d00d5464 | 584 | |
2638a03a VM |
585 | if (git_buf_printf(&path, "%s/refs", backend->path) < 0) |
586 | return -1; | |
d00d5464 | 587 | |
2638a03a VM |
588 | if (git_iterator_for_filesystem(&fsit, git_buf_cstr(&path), 0, NULL, NULL) < 0) |
589 | return -1; | |
4dcd8780 | 590 | |
2638a03a VM |
591 | git_vector_init(&iter->loose, 8, NULL); |
592 | git_buf_sets(&path, GIT_REFS_DIR); | |
d00d5464 | 593 | |
2638a03a VM |
594 | while (!git_iterator_current(&entry, fsit) && entry) { |
595 | const char *ref_name; | |
4def7035 | 596 | khiter_t pos; |
d00d5464 | 597 | |
2638a03a VM |
598 | git_buf_truncate(&path, strlen(GIT_REFS_DIR)); |
599 | git_buf_puts(&path, entry->path); | |
600 | ref_name = git_buf_cstr(&path); | |
4dcd8780 | 601 | |
ec24e542 VM |
602 | if (git__suffixcmp(ref_name, ".lock") == 0 || |
603 | (iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0)) { | |
2638a03a VM |
604 | git_iterator_advance(NULL, fsit); |
605 | continue; | |
606 | } | |
d00d5464 | 607 | |
2638a03a VM |
608 | pos = git_strmap_lookup_index(packfile, ref_name); |
609 | if (git_strmap_valid_index(packfile, pos)) { | |
610 | struct packref *ref = git_strmap_value_at(packfile, pos); | |
611 | ref->flags |= PACKREF_SHADOWED; | |
612 | } | |
d00d5464 | 613 | |
2638a03a VM |
614 | git_vector_insert(&iter->loose, git__strdup(ref_name)); |
615 | git_iterator_advance(NULL, fsit); | |
616 | } | |
d00d5464 | 617 | |
2638a03a VM |
618 | git_iterator_free(fsit); |
619 | git_buf_free(&path); | |
4def7035 CMN |
620 | |
621 | return 0; | |
622 | } | |
623 | ||
ec24e542 VM |
624 | static int refdb_fs_backend__iterator_next( |
625 | git_reference **out, git_reference_iterator *_iter) | |
4def7035 | 626 | { |
2638a03a | 627 | refdb_fs_iter *iter = (refdb_fs_iter *)_iter; |
ec24e542 | 628 | refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend; |
2638a03a | 629 | git_strmap *packfile = backend->refcache.packfile; |
4def7035 | 630 | |
56960b83 | 631 | while (iter->loose_pos < iter->loose.length) { |
2638a03a | 632 | const char *path = git_vector_get(&iter->loose, iter->loose_pos++); |
56960b83 VM |
633 | |
634 | if (loose_lookup(out, backend, path) == 0) | |
635 | return 0; | |
636 | ||
637 | giterr_clear(); | |
2638a03a | 638 | } |
d00d5464 | 639 | |
ec24e542 | 640 | while (iter->packed_pos < kh_end(packfile)) { |
2638a03a | 641 | struct packref *ref = NULL; |
d00d5464 | 642 | |
ec24e542 | 643 | while (!kh_exist(packfile, iter->packed_pos)) { |
2638a03a | 644 | iter->packed_pos++; |
ec24e542 VM |
645 | if (iter->packed_pos == kh_end(packfile)) |
646 | return GIT_ITEROVER; | |
647 | } | |
648 | ||
649 | ref = kh_val(packfile, iter->packed_pos); | |
650 | iter->packed_pos++; | |
651 | ||
652 | if (ref->flags & PACKREF_SHADOWED) | |
653 | continue; | |
654 | ||
655 | if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0) | |
656 | continue; | |
99d32707 | 657 | |
56960b83 VM |
658 | *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel); |
659 | if (*out == NULL) | |
660 | return -1; | |
661 | ||
2638a03a | 662 | return 0; |
4def7035 CMN |
663 | } |
664 | ||
2638a03a | 665 | return GIT_ITEROVER; |
d00d5464 ET |
666 | } |
667 | ||
ec24e542 VM |
668 | static int refdb_fs_backend__iterator_next_name( |
669 | const char **out, git_reference_iterator *_iter) | |
670 | { | |
671 | refdb_fs_iter *iter = (refdb_fs_iter *)_iter; | |
672 | refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend; | |
673 | git_strmap *packfile = backend->refcache.packfile; | |
674 | ||
675 | while (iter->loose_pos < iter->loose.length) { | |
676 | const char *path = git_vector_get(&iter->loose, iter->loose_pos++); | |
677 | ||
678 | if (git_strmap_exists(packfile, path)) | |
679 | continue; | |
680 | ||
681 | *out = path; | |
682 | return 0; | |
683 | } | |
684 | ||
685 | while (iter->packed_pos < kh_end(packfile)) { | |
686 | while (!kh_exist(packfile, iter->packed_pos)) { | |
687 | iter->packed_pos++; | |
688 | if (iter->packed_pos == kh_end(packfile)) | |
689 | return GIT_ITEROVER; | |
690 | } | |
691 | ||
692 | *out = kh_key(packfile, iter->packed_pos); | |
693 | iter->packed_pos++; | |
694 | ||
695 | if (iter->glob && p_fnmatch(iter->glob, *out, 0) != 0) | |
696 | continue; | |
697 | ||
698 | return 0; | |
699 | } | |
700 | ||
701 | return GIT_ITEROVER; | |
702 | } | |
703 | ||
704 | static int refdb_fs_backend__iterator( | |
705 | git_reference_iterator **out, git_refdb_backend *_backend, const char *glob) | |
706 | { | |
707 | refdb_fs_iter *iter; | |
708 | refdb_fs_backend *backend; | |
709 | ||
710 | assert(_backend); | |
711 | backend = (refdb_fs_backend *)_backend; | |
712 | ||
713 | if (packed_load(backend) < 0) | |
714 | return -1; | |
715 | ||
716 | iter = git__calloc(1, sizeof(refdb_fs_iter)); | |
717 | GITERR_CHECK_ALLOC(iter); | |
718 | ||
719 | if (glob != NULL) | |
720 | iter->glob = git__strdup(glob); | |
721 | ||
722 | iter->parent.next = refdb_fs_backend__iterator_next; | |
723 | iter->parent.next_name = refdb_fs_backend__iterator_next_name; | |
724 | iter->parent.free = refdb_fs_backend__iterator_free; | |
725 | ||
726 | if (iter_load_loose_paths(backend, iter) < 0) { | |
727 | refdb_fs_backend__iterator_free((git_reference_iterator *)iter); | |
728 | return -1; | |
729 | } | |
730 | ||
731 | *out = (git_reference_iterator *)iter; | |
732 | return 0; | |
733 | } | |
734 | ||
4e6e2ff2 VM |
735 | static bool ref_is_available( |
736 | const char *old_ref, const char *new_ref, const char *this_ref) | |
737 | { | |
738 | if (old_ref == NULL || strcmp(old_ref, this_ref)) { | |
739 | size_t reflen = strlen(this_ref); | |
740 | size_t newlen = strlen(new_ref); | |
741 | size_t cmplen = reflen < newlen ? reflen : newlen; | |
742 | const char *lead = reflen < newlen ? new_ref : this_ref; | |
743 | ||
744 | if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') { | |
745 | return false; | |
746 | } | |
747 | } | |
748 | ||
749 | return true; | |
750 | } | |
751 | ||
752 | static int reference_path_available( | |
753 | refdb_fs_backend *backend, | |
754 | const char *new_ref, | |
755 | const char* old_ref, | |
756 | int force) | |
757 | { | |
758 | struct packref *this_ref; | |
759 | ||
760 | if (packed_load(backend) < 0) | |
761 | return -1; | |
762 | ||
763 | if (!force) { | |
764 | int exists; | |
765 | ||
766 | if (refdb_fs_backend__exists(&exists, (git_refdb_backend *)backend, new_ref) < 0) | |
767 | return -1; | |
768 | ||
769 | if (exists) { | |
770 | giterr_set(GITERR_REFERENCE, | |
771 | "Failed to write reference '%s': a reference with " | |
772 | " that name already exists.", new_ref); | |
773 | return GIT_EEXISTS; | |
774 | } | |
775 | } | |
776 | ||
777 | git_strmap_foreach_value(backend->refcache.packfile, this_ref, { | |
778 | if (!ref_is_available(old_ref, new_ref, this_ref->name)) { | |
779 | giterr_set(GITERR_REFERENCE, | |
780 | "The path to reference '%s' collides with an existing one", new_ref); | |
781 | return -1; | |
782 | } | |
783 | }); | |
784 | ||
785 | return 0; | |
786 | } | |
ec24e542 | 787 | |
d00d5464 ET |
788 | static int loose_write(refdb_fs_backend *backend, const git_reference *ref) |
789 | { | |
790 | git_filebuf file = GIT_FILEBUF_INIT; | |
791 | git_buf ref_path = GIT_BUF_INIT; | |
792 | ||
793 | /* Remove a possibly existing empty directory hierarchy | |
794 | * which name would collide with the reference name | |
795 | */ | |
4e6e2ff2 | 796 | if (git_futils_rmdir_r(ref->name, backend->path, GIT_RMDIR_SKIP_NONEMPTY) < 0) |
d00d5464 ET |
797 | return -1; |
798 | ||
799 | if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0) | |
800 | return -1; | |
801 | ||
802 | if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { | |
803 | git_buf_free(&ref_path); | |
804 | return -1; | |
805 | } | |
806 | ||
807 | git_buf_free(&ref_path); | |
808 | ||
809 | if (ref->type == GIT_REF_OID) { | |
810 | char oid[GIT_OID_HEXSZ + 1]; | |
811 | ||
fedd0f9e | 812 | git_oid_fmt(oid, &ref->target.oid); |
d00d5464 ET |
813 | oid[GIT_OID_HEXSZ] = '\0'; |
814 | ||
815 | git_filebuf_printf(&file, "%s\n", oid); | |
816 | ||
817 | } else if (ref->type == GIT_REF_SYMBOLIC) { | |
818 | git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); | |
819 | } else { | |
820 | assert(0); /* don't let this happen */ | |
821 | } | |
822 | ||
823 | return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); | |
824 | } | |
825 | ||
826 | static int packed_sort(const void *a, const void *b) | |
827 | { | |
828 | const struct packref *ref_a = (const struct packref *)a; | |
829 | const struct packref *ref_b = (const struct packref *)b; | |
830 | ||
831 | return strcmp(ref_a->name, ref_b->name); | |
832 | } | |
833 | ||
834 | /* | |
835 | * Find out what object this reference resolves to. | |
836 | * | |
837 | * For references that point to a 'big' tag (e.g. an | |
838 | * actual tag object on the repository), we need to | |
839 | * cache on the packfile the OID of the object to | |
840 | * which that 'big tag' is pointing to. | |
841 | */ | |
842 | static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref) | |
843 | { | |
844 | git_object *object; | |
845 | ||
f69db390 | 846 | if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL) |
d00d5464 ET |
847 | return 0; |
848 | ||
d00d5464 ET |
849 | /* |
850 | * Find the tagged object in the repository | |
851 | */ | |
852 | if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJ_ANY) < 0) | |
853 | return -1; | |
854 | ||
855 | /* | |
856 | * If the tagged object is a Tag object, we need to resolve it; | |
857 | * if the ref is actually a 'weak' ref, we don't need to resolve | |
858 | * anything. | |
859 | */ | |
860 | if (git_object_type(object) == GIT_OBJ_TAG) { | |
861 | git_tag *tag = (git_tag *)object; | |
862 | ||
863 | /* | |
864 | * Find the object pointed at by this tag | |
865 | */ | |
866 | git_oid_cpy(&ref->peel, git_tag_target_id(tag)); | |
f69db390 | 867 | ref->flags |= PACKREF_HAS_PEEL; |
d00d5464 ET |
868 | |
869 | /* | |
870 | * The reference has now cached the resolved OID, and is | |
871 | * marked at such. When written to the packfile, it'll be | |
872 | * accompanied by this resolved oid | |
873 | */ | |
874 | } | |
875 | ||
876 | git_object_free(object); | |
877 | return 0; | |
878 | } | |
879 | ||
880 | /* | |
881 | * Write a single reference into a packfile | |
882 | */ | |
883 | static int packed_write_ref(struct packref *ref, git_filebuf *file) | |
884 | { | |
885 | char oid[GIT_OID_HEXSZ + 1]; | |
886 | ||
887 | git_oid_fmt(oid, &ref->oid); | |
888 | oid[GIT_OID_HEXSZ] = 0; | |
889 | ||
890 | /* | |
891 | * For references that peel to an object in the repo, we must | |
892 | * write the resulting peel on a separate line, e.g. | |
893 | * | |
894 | * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4 | |
895 | * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100 | |
896 | * | |
897 | * This obviously only applies to tags. | |
898 | * The required peels have already been loaded into `ref->peel_target`. | |
899 | */ | |
f69db390 | 900 | if (ref->flags & PACKREF_HAS_PEEL) { |
d00d5464 ET |
901 | char peel[GIT_OID_HEXSZ + 1]; |
902 | git_oid_fmt(peel, &ref->peel); | |
903 | peel[GIT_OID_HEXSZ] = 0; | |
904 | ||
905 | if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) | |
906 | return -1; | |
907 | } else { | |
908 | if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) | |
909 | return -1; | |
910 | } | |
911 | ||
912 | return 0; | |
913 | } | |
914 | ||
915 | /* | |
916 | * Remove all loose references | |
917 | * | |
918 | * Once we have successfully written a packfile, | |
919 | * all the loose references that were packed must be | |
920 | * removed from disk. | |
921 | * | |
922 | * This is a dangerous method; make sure the packfile | |
923 | * is well-written, because we are destructing references | |
924 | * here otherwise. | |
925 | */ | |
926 | static int packed_remove_loose( | |
927 | refdb_fs_backend *backend, | |
928 | git_vector *packing_list) | |
929 | { | |
10c06114 | 930 | size_t i; |
d00d5464 ET |
931 | git_buf full_path = GIT_BUF_INIT; |
932 | int failed = 0; | |
933 | ||
934 | for (i = 0; i < packing_list->length; ++i) { | |
935 | struct packref *ref = git_vector_get(packing_list, i); | |
936 | ||
f69db390 | 937 | if ((ref->flags & PACKREF_WAS_LOOSE) == 0) |
d00d5464 ET |
938 | continue; |
939 | ||
940 | if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0) | |
941 | return -1; /* critical; do not try to recover on oom */ | |
942 | ||
943 | if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) { | |
944 | if (failed) | |
945 | continue; | |
946 | ||
947 | giterr_set(GITERR_REFERENCE, | |
948 | "Failed to remove loose reference '%s' after packing: %s", | |
949 | full_path.ptr, strerror(errno)); | |
950 | ||
951 | failed = 1; | |
952 | } | |
953 | ||
954 | /* | |
955 | * if we fail to remove a single file, this is *not* good, | |
956 | * but we should keep going and remove as many as possible. | |
957 | * After we've removed as many files as possible, we return | |
958 | * the error code anyway. | |
959 | */ | |
960 | } | |
961 | ||
962 | git_buf_free(&full_path); | |
963 | return failed ? -1 : 0; | |
964 | } | |
965 | ||
966 | /* | |
967 | * Write all the contents in the in-memory packfile to disk. | |
968 | */ | |
969 | static int packed_write(refdb_fs_backend *backend) | |
970 | { | |
971 | git_filebuf pack_file = GIT_FILEBUF_INIT; | |
10c06114 | 972 | size_t i; |
d00d5464 ET |
973 | git_buf pack_file_path = GIT_BUF_INIT; |
974 | git_vector packing_list; | |
975 | unsigned int total_refs; | |
976 | ||
977 | assert(backend && backend->refcache.packfile); | |
978 | ||
979 | total_refs = | |
980 | (unsigned int)git_strmap_num_entries(backend->refcache.packfile); | |
981 | ||
982 | if (git_vector_init(&packing_list, total_refs, packed_sort) < 0) | |
983 | return -1; | |
984 | ||
985 | /* Load all the packfile into a vector */ | |
986 | { | |
987 | struct packref *reference; | |
988 | ||
989 | /* cannot fail: vector already has the right size */ | |
990 | git_strmap_foreach_value(backend->refcache.packfile, reference, { | |
991 | git_vector_insert(&packing_list, reference); | |
992 | }); | |
993 | } | |
994 | ||
995 | /* sort the vector so the entries appear sorted on the packfile */ | |
996 | git_vector_sort(&packing_list); | |
997 | ||
998 | /* Now we can open the file! */ | |
999 | if (git_buf_joinpath(&pack_file_path, | |
1000 | backend->path, GIT_PACKEDREFS_FILE) < 0) | |
1001 | goto cleanup_memory; | |
1002 | ||
1003 | if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0) | |
1004 | goto cleanup_packfile; | |
1005 | ||
1006 | /* Packfiles have a header... apparently | |
1007 | * This is in fact not required, but we might as well print it | |
1008 | * just for kicks */ | |
1009 | if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) | |
1010 | goto cleanup_packfile; | |
1011 | ||
1012 | for (i = 0; i < packing_list.length; ++i) { | |
1013 | struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); | |
1014 | ||
1015 | if (packed_find_peel(backend, ref) < 0) | |
1016 | goto cleanup_packfile; | |
1017 | ||
1018 | if (packed_write_ref(ref, &pack_file) < 0) | |
1019 | goto cleanup_packfile; | |
1020 | } | |
1021 | ||
1022 | /* if we've written all the references properly, we can commit | |
1023 | * the packfile to make the changes effective */ | |
1024 | if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0) | |
1025 | goto cleanup_memory; | |
1026 | ||
1027 | /* when and only when the packfile has been properly written, | |
1028 | * we can go ahead and remove the loose refs */ | |
1029 | if (packed_remove_loose(backend, &packing_list) < 0) | |
1030 | goto cleanup_memory; | |
1031 | ||
1032 | { | |
1033 | struct stat st; | |
1034 | if (p_stat(pack_file_path.ptr, &st) == 0) | |
1035 | backend->refcache.packfile_time = st.st_mtime; | |
1036 | } | |
1037 | ||
1038 | git_vector_free(&packing_list); | |
1039 | git_buf_free(&pack_file_path); | |
1040 | ||
1041 | /* we're good now */ | |
1042 | return 0; | |
1043 | ||
1044 | cleanup_packfile: | |
1045 | git_filebuf_cleanup(&pack_file); | |
1046 | ||
1047 | cleanup_memory: | |
1048 | git_vector_free(&packing_list); | |
1049 | git_buf_free(&pack_file_path); | |
1050 | ||
1051 | return -1; | |
1052 | } | |
1053 | ||
1054 | static int refdb_fs_backend__write( | |
1055 | git_refdb_backend *_backend, | |
4e6e2ff2 VM |
1056 | const git_reference *ref, |
1057 | int force) | |
d00d5464 ET |
1058 | { |
1059 | refdb_fs_backend *backend; | |
4e6e2ff2 | 1060 | int error; |
d00d5464 ET |
1061 | |
1062 | assert(_backend); | |
1063 | backend = (refdb_fs_backend *)_backend; | |
1064 | ||
4e6e2ff2 VM |
1065 | error = reference_path_available(backend, ref->name, NULL, force); |
1066 | if (error < 0) | |
1067 | return error; | |
1068 | ||
d00d5464 ET |
1069 | return loose_write(backend, ref); |
1070 | } | |
1071 | ||
1072 | static int refdb_fs_backend__delete( | |
1073 | git_refdb_backend *_backend, | |
4e6e2ff2 | 1074 | const char *ref_name) |
d00d5464 ET |
1075 | { |
1076 | refdb_fs_backend *backend; | |
d00d5464 ET |
1077 | git_buf loose_path = GIT_BUF_INIT; |
1078 | struct packref *pack_ref; | |
1079 | khiter_t pack_ref_pos; | |
4e6e2ff2 | 1080 | int error = 0; |
038c1654 | 1081 | bool loose_deleted = 0; |
d00d5464 ET |
1082 | |
1083 | assert(_backend); | |
4e6e2ff2 | 1084 | assert(ref_name); |
d00d5464 ET |
1085 | |
1086 | backend = (refdb_fs_backend *)_backend; | |
d00d5464 ET |
1087 | |
1088 | /* If a loose reference exists, remove it from the filesystem */ | |
4e6e2ff2 | 1089 | if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0) |
d00d5464 ET |
1090 | return -1; |
1091 | ||
1092 | if (git_path_isfile(loose_path.ptr)) { | |
1093 | error = p_unlink(loose_path.ptr); | |
1094 | loose_deleted = 1; | |
1095 | } | |
4dcd8780 | 1096 | |
d00d5464 ET |
1097 | git_buf_free(&loose_path); |
1098 | ||
1099 | if (error != 0) | |
1100 | return error; | |
1101 | ||
1102 | /* If a packed reference exists, remove it from the packfile and repack */ | |
4e6e2ff2 VM |
1103 | error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref_name); |
1104 | ||
1105 | if (error == GIT_ENOTFOUND) | |
1106 | return loose_deleted ? 0 : GIT_ENOTFOUND; | |
d00d5464 | 1107 | |
4e6e2ff2 | 1108 | if (error == 0) { |
d00d5464 ET |
1109 | git_strmap_delete_at(backend->refcache.packfile, pack_ref_pos); |
1110 | git__free(pack_ref); | |
d00d5464 ET |
1111 | error = packed_write(backend); |
1112 | } | |
4dcd8780 | 1113 | |
d00d5464 ET |
1114 | return error; |
1115 | } | |
1116 | ||
4e6e2ff2 VM |
1117 | static int refdb_fs_backend__rename( |
1118 | git_reference **out, | |
1119 | git_refdb_backend *_backend, | |
1120 | const char *old_name, | |
1121 | const char *new_name, | |
1122 | int force) | |
1123 | { | |
1124 | refdb_fs_backend *backend; | |
1125 | git_reference *old, *new; | |
1126 | int error; | |
1127 | ||
1128 | assert(_backend); | |
1129 | backend = (refdb_fs_backend *)_backend; | |
1130 | ||
1131 | error = reference_path_available(backend, new_name, old_name, force); | |
1132 | if (error < 0) | |
1133 | return error; | |
1134 | ||
1135 | error = refdb_fs_backend__lookup(&old, _backend, old_name); | |
1136 | if (error < 0) | |
1137 | return error; | |
1138 | ||
1139 | error = refdb_fs_backend__delete(_backend, old_name); | |
1140 | if (error < 0) { | |
1141 | git_reference_free(old); | |
1142 | return error; | |
1143 | } | |
1144 | ||
1145 | new = realloc(old, sizeof(git_reference) + strlen(new_name) + 1); | |
1146 | memcpy(new->name, new_name, strlen(new_name) + 1); | |
1147 | ||
1148 | error = loose_write(backend, new); | |
1149 | if (error < 0) { | |
1150 | git_reference_free(new); | |
1151 | return error; | |
1152 | } | |
1153 | ||
1154 | if (out) { | |
1155 | *out = new; | |
1156 | } else { | |
1157 | git_reference_free(new); | |
1158 | } | |
1159 | ||
1160 | return 0; | |
1161 | } | |
1162 | ||
d00d5464 ET |
1163 | static int refdb_fs_backend__compress(git_refdb_backend *_backend) |
1164 | { | |
1165 | refdb_fs_backend *backend; | |
1166 | ||
1167 | assert(_backend); | |
1168 | backend = (refdb_fs_backend *)_backend; | |
1169 | ||
1170 | if (packed_load(backend) < 0 || /* load the existing packfile */ | |
1171 | packed_loadloose(backend) < 0 || /* add all the loose refs */ | |
1172 | packed_write(backend) < 0) /* write back to disk */ | |
1173 | return -1; | |
1174 | ||
1175 | return 0; | |
1176 | } | |
1177 | ||
1178 | static void refcache_free(git_refcache *refs) | |
1179 | { | |
1180 | assert(refs); | |
1181 | ||
1182 | if (refs->packfile) { | |
1183 | struct packref *reference; | |
1184 | ||
1185 | git_strmap_foreach_value(refs->packfile, reference, { | |
1186 | git__free(reference); | |
1187 | }); | |
1188 | ||
1189 | git_strmap_free(refs->packfile); | |
1190 | } | |
1191 | } | |
1192 | ||
1193 | static void refdb_fs_backend__free(git_refdb_backend *_backend) | |
1194 | { | |
1195 | refdb_fs_backend *backend; | |
1196 | ||
1197 | assert(_backend); | |
1198 | backend = (refdb_fs_backend *)_backend; | |
1199 | ||
1200 | refcache_free(&backend->refcache); | |
bade5194 | 1201 | git__free(backend->path); |
d00d5464 ET |
1202 | git__free(backend); |
1203 | } | |
1204 | ||
8cddf9b8 VM |
1205 | static int setup_namespace(git_buf *path, git_repository *repo) |
1206 | { | |
1fed6b07 | 1207 | char *parts, *start, *end; |
8cddf9b8 | 1208 | |
69a3c766 CMN |
1209 | /* Not all repositories have a path */ |
1210 | if (repo->path_repository == NULL) | |
1211 | return 0; | |
1212 | ||
8cddf9b8 VM |
1213 | /* Load the path to the repo first */ |
1214 | git_buf_puts(path, repo->path_repository); | |
1215 | ||
1216 | /* if the repo is not namespaced, nothing else to do */ | |
1217 | if (repo->namespace == NULL) | |
1218 | return 0; | |
1219 | ||
1220 | parts = end = git__strdup(repo->namespace); | |
1221 | if (parts == NULL) | |
1222 | return -1; | |
1223 | ||
1224 | /** | |
1225 | * From `man gitnamespaces`: | |
1226 | * namespaces which include a / will expand to a hierarchy | |
1227 | * of namespaces; for example, GIT_NAMESPACE=foo/bar will store | |
1228 | * refs under refs/namespaces/foo/refs/namespaces/bar/ | |
1229 | */ | |
1230 | while ((start = git__strsep(&end, "/")) != NULL) { | |
1231 | git_buf_printf(path, "refs/namespaces/%s/", start); | |
1232 | } | |
1233 | ||
1234 | git_buf_printf(path, "refs/namespaces/%s/refs", end); | |
1235 | free(parts); | |
1236 | ||
1237 | /* Make sure that the folder with the namespace exists */ | |
1fed6b07 | 1238 | if (git_futils_mkdir_r(git_buf_cstr(path), repo->path_repository, 0777) < 0) |
8cddf9b8 VM |
1239 | return -1; |
1240 | ||
1241 | /* Return the root of the namespaced path, i.e. without the trailing '/refs' */ | |
1242 | git_buf_rtruncate_at_char(path, '/'); | |
1243 | return 0; | |
1244 | } | |
1245 | ||
d00d5464 ET |
1246 | int git_refdb_backend_fs( |
1247 | git_refdb_backend **backend_out, | |
4e4eab52 | 1248 | git_repository *repository) |
d00d5464 | 1249 | { |
bade5194 | 1250 | git_buf path = GIT_BUF_INIT; |
d00d5464 ET |
1251 | refdb_fs_backend *backend; |
1252 | ||
1253 | backend = git__calloc(1, sizeof(refdb_fs_backend)); | |
1254 | GITERR_CHECK_ALLOC(backend); | |
1255 | ||
1256 | backend->repo = repository; | |
bade5194 | 1257 | |
8cddf9b8 VM |
1258 | if (setup_namespace(&path, repository) < 0) { |
1259 | git__free(backend); | |
1260 | return -1; | |
1261 | } | |
bade5194 VM |
1262 | |
1263 | backend->path = git_buf_detach(&path); | |
d00d5464 ET |
1264 | |
1265 | backend->parent.exists = &refdb_fs_backend__exists; | |
1266 | backend->parent.lookup = &refdb_fs_backend__lookup; | |
4def7035 | 1267 | backend->parent.iterator = &refdb_fs_backend__iterator; |
d00d5464 ET |
1268 | backend->parent.write = &refdb_fs_backend__write; |
1269 | backend->parent.delete = &refdb_fs_backend__delete; | |
4e6e2ff2 | 1270 | backend->parent.rename = &refdb_fs_backend__rename; |
d00d5464 ET |
1271 | backend->parent.compress = &refdb_fs_backend__compress; |
1272 | backend->parent.free = &refdb_fs_backend__free; | |
1273 | ||
1274 | *backend_out = (git_refdb_backend *)backend; | |
1275 | return 0; | |
1276 | } |