]>
Commit | Line | Data |
---|---|---|
9282e921 | 1 | /* |
5e0de328 | 2 | * Copyright (C) 2009-2012 the libgit2 contributors |
9282e921 | 3 | * |
bb742ede VM |
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. | |
9282e921 | 6 | */ |
7 | ||
8 | #include "refs.h" | |
9 | #include "hash.h" | |
10 | #include "repository.h" | |
11 | #include "fileops.h" | |
01ad7b3a | 12 | #include "pack.h" |
a5cd086d | 13 | #include "reflog.h" |
9282e921 | 14 | |
87d3acf4 VM |
15 | #include <git2/tag.h> |
16 | #include <git2/object.h> | |
17 | ||
9282e921 | 18 | #define MAX_NESTING_LEVEL 5 |
19 | ||
d4a0b124 VM |
20 | enum { |
21 | GIT_PACKREF_HAS_PEEL = 1, | |
22 | GIT_PACKREF_WAS_LOOSE = 2 | |
23 | }; | |
86194b24 | 24 | |
d4a0b124 VM |
25 | struct packref { |
26 | git_oid oid; | |
27 | git_oid peel; | |
28 | char flags; | |
29 | char name[GIT_FLEX_ARRAY]; | |
30 | }; | |
86194b24 | 31 | |
9282e921 | 32 | static const int default_table_size = 32; |
33 | ||
d4a0b124 | 34 | static int reference_read( |
13224ea4 | 35 | git_buf *file_content, |
d4a0b124 VM |
36 | time_t *mtime, |
37 | const char *repo_path, | |
38 | const char *ref_name, | |
39 | int *updated); | |
a46ec457 | 40 | |
87d3acf4 | 41 | /* loose refs */ |
13224ea4 VM |
42 | static int loose_parse_symbolic(git_reference *ref, git_buf *file_content); |
43 | static int loose_parse_oid(git_oid *ref, git_buf *file_content); | |
d4a0b124 VM |
44 | static int loose_lookup(git_reference *ref); |
45 | static int loose_lookup_to_packfile(struct packref **ref_out, | |
46 | git_repository *repo, const char *name); | |
47 | static int loose_write(git_reference *ref); | |
87d3acf4 VM |
48 | |
49 | /* packed refs */ | |
d4a0b124 VM |
50 | static int packed_parse_peel(struct packref *tag_ref, |
51 | const char **buffer_out, const char *buffer_end); | |
52 | static int packed_parse_oid(struct packref **ref_out, | |
53 | const char **buffer_out, const char *buffer_end); | |
87d3acf4 VM |
54 | static int packed_load(git_repository *repo); |
55 | static int packed_loadloose(git_repository *repository); | |
d4a0b124 VM |
56 | static int packed_write_ref(struct packref *ref, git_filebuf *file); |
57 | static int packed_find_peel(git_repository *repo, struct packref *ref); | |
87d3acf4 VM |
58 | static int packed_remove_loose(git_repository *repo, git_vector *packing_list); |
59 | static int packed_sort(const void *a, const void *b); | |
d4a0b124 | 60 | static int packed_lookup(git_reference *ref); |
87d3acf4 VM |
61 | static int packed_write(git_repository *repo); |
62 | ||
95cde17c | 63 | /* internal helpers */ |
1a481123 VM |
64 | static int reference_path_available(git_repository *repo, |
65 | const char *ref, const char *old_ref); | |
d4a0b124 VM |
66 | static int reference_delete(git_reference *ref); |
67 | static int reference_lookup(git_reference *ref); | |
95cde17c | 68 | |
87d3acf4 | 69 | /* name normalization */ |
d4a0b124 VM |
70 | static int normalize_name(char *buffer_out, size_t out_size, |
71 | const char *name, int is_oid_ref); | |
a46ec457 MS |
72 | |
73 | ||
d4a0b124 | 74 | void git_reference_free(git_reference *reference) |
9282e921 | 75 | { |
2f8a8ab2 VM |
76 | if (reference == NULL) |
77 | return; | |
9282e921 | 78 | |
d4a0b124 | 79 | git__free(reference->name); |
97769280 | 80 | reference->name = NULL; |
9282e921 | 81 | |
97769280 | 82 | if (reference->flags & GIT_REF_SYMBOLIC) { |
d4a0b124 | 83 | git__free(reference->target.symbolic); |
97769280 RB |
84 | reference->target.symbolic = NULL; |
85 | } | |
9282e921 | 86 | |
3286c408 | 87 | git__free(reference); |
9282e921 | 88 | } |
89 | ||
fa515656 | 90 | static int reference_alloc( |
d4a0b124 | 91 | git_reference **ref_out, |
87d3acf4 | 92 | git_repository *repo, |
d4a0b124 | 93 | const char *name) |
87d3acf4 | 94 | { |
d4a0b124 | 95 | git_reference *reference = NULL; |
9282e921 | 96 | |
1d8cc731 | 97 | assert(ref_out && repo && name); |
98 | ||
d4a0b124 | 99 | reference = git__malloc(sizeof(git_reference)); |
1a481123 | 100 | GITERR_CHECK_ALLOC(reference); |
9282e921 | 101 | |
d4a0b124 | 102 | memset(reference, 0x0, sizeof(git_reference)); |
2f8a8ab2 | 103 | reference->owner = repo; |
1d8cc731 | 104 | |
d4a0b124 | 105 | reference->name = git__strdup(name); |
1a481123 | 106 | GITERR_CHECK_ALLOC(reference->name); |
9282e921 | 107 | |
2f8a8ab2 | 108 | *ref_out = reference; |
1a481123 | 109 | return 0; |
1d8cc731 | 110 | } |
111 | ||
1a481123 VM |
112 | static int reference_read( |
113 | git_buf *file_content, | |
114 | time_t *mtime, | |
115 | const char *repo_path, | |
116 | const char *ref_name, | |
117 | int *updated) | |
7341bf87 | 118 | { |
97769280 | 119 | git_buf path = GIT_BUF_INIT; |
1a481123 | 120 | int result; |
7341bf87 | 121 | |
c4982328 CMN |
122 | assert(file_content && repo_path && ref_name); |
123 | ||
7341bf87 | 124 | /* Determine the full path of the file */ |
1a481123 VM |
125 | if (git_buf_joinpath(&path, repo_path, ref_name) < 0) |
126 | return -1; | |
97769280 | 127 | |
1a481123 | 128 | result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated); |
97769280 | 129 | git_buf_free(&path); |
1a481123 | 130 | return result; |
7341bf87 VM |
131 | } |
132 | ||
13224ea4 | 133 | static int loose_parse_symbolic(git_reference *ref, git_buf *file_content) |
2f8a8ab2 | 134 | { |
ddc9e79a | 135 | const unsigned int header_len = strlen(GIT_SYMREF); |
2f8a8ab2 VM |
136 | const char *refname_start; |
137 | char *eol; | |
9282e921 | 138 | |
13224ea4 | 139 | refname_start = (const char *)file_content->ptr; |
9282e921 | 140 | |
13224ea4 | 141 | if (file_content->size < (header_len + 1)) |
1a481123 | 142 | goto corrupt; |
9282e921 | 143 | |
932d1baf | 144 | /* |
2f8a8ab2 | 145 | * Assume we have already checked for the header |
932d1baf | 146 | * before calling this function |
2f8a8ab2 | 147 | */ |
2f8a8ab2 | 148 | refname_start += header_len; |
9282e921 | 149 | |
d4a0b124 | 150 | ref->target.symbolic = git__strdup(refname_start); |
1a481123 | 151 | GITERR_CHECK_ALLOC(ref->target.symbolic); |
9282e921 | 152 | |
2f8a8ab2 | 153 | /* remove newline at the end of file */ |
d4a0b124 | 154 | eol = strchr(ref->target.symbolic, '\n'); |
ff5873ad | 155 | if (eol == NULL) |
1a481123 | 156 | goto corrupt; |
ff5873ad VM |
157 | |
158 | *eol = '\0'; | |
159 | if (eol[-1] == '\r') | |
160 | eol[-1] = '\0'; | |
9282e921 | 161 | |
1a481123 VM |
162 | return 0; |
163 | ||
164 | corrupt: | |
165 | giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); | |
166 | return -1; | |
9282e921 | 167 | } |
168 | ||
13224ea4 | 169 | static int loose_parse_oid(git_oid *oid, git_buf *file_content) |
2f8a8ab2 VM |
170 | { |
171 | char *buffer; | |
86194b24 | 172 | |
13224ea4 | 173 | buffer = (char *)file_content->ptr; |
9282e921 | 174 | |
2f8a8ab2 | 175 | /* File format: 40 chars (OID) + newline */ |
13224ea4 | 176 | if (file_content->size < GIT_OID_HEXSZ + 1) |
1a481123 | 177 | goto corrupt; |
9282e921 | 178 | |
1a481123 VM |
179 | if (git_oid_fromstr(oid, buffer) < 0) |
180 | goto corrupt; | |
9282e921 | 181 | |
ff5873ad VM |
182 | buffer = buffer + GIT_OID_HEXSZ; |
183 | if (*buffer == '\r') | |
184 | buffer++; | |
185 | ||
186 | if (*buffer != '\n') | |
1a481123 | 187 | goto corrupt; |
9282e921 | 188 | |
0d0fa7c3 | 189 | return 0; |
1a481123 VM |
190 | |
191 | corrupt: | |
192 | giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); | |
193 | return -1; | |
9282e921 | 194 | } |
195 | ||
97769280 | 196 | static git_rtype loose_guess_rtype(const git_buf *full_path) |
00571828 | 197 | { |
13224ea4 | 198 | git_buf ref_file = GIT_BUF_INIT; |
00571828 VM |
199 | git_rtype type; |
200 | ||
201 | type = GIT_REF_INVALID; | |
202 | ||
0d0fa7c3 | 203 | if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) { |
13224ea4 | 204 | if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) |
00571828 VM |
205 | type = GIT_REF_SYMBOLIC; |
206 | else | |
207 | type = GIT_REF_OID; | |
208 | } | |
209 | ||
13224ea4 | 210 | git_buf_free(&ref_file); |
00571828 VM |
211 | return type; |
212 | } | |
213 | ||
d4a0b124 VM |
214 | static int loose_lookup(git_reference *ref) |
215 | { | |
1a481123 | 216 | int result, updated; |
13224ea4 | 217 | git_buf ref_file = GIT_BUF_INIT; |
d4a0b124 | 218 | |
1a481123 VM |
219 | result = reference_read(&ref_file, &ref->mtime, |
220 | ref->owner->path_repository, ref->name, &updated); | |
221 | ||
222 | if (result < 0) | |
223 | return result; | |
d4a0b124 VM |
224 | |
225 | if (!updated) | |
1a481123 | 226 | return 0; |
d4a0b124 | 227 | |
fa515656 | 228 | if (ref->flags & GIT_REF_SYMBOLIC) { |
854eccbb | 229 | git__free(ref->target.symbolic); |
fa515656 VM |
230 | ref->target.symbolic = NULL; |
231 | } | |
d4a0b124 VM |
232 | |
233 | ref->flags = 0; | |
234 | ||
13224ea4 | 235 | if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { |
d4a0b124 | 236 | ref->flags |= GIT_REF_SYMBOLIC; |
1a481123 | 237 | result = loose_parse_symbolic(ref, &ref_file); |
d4a0b124 VM |
238 | } else { |
239 | ref->flags |= GIT_REF_OID; | |
1a481123 | 240 | result = loose_parse_oid(&ref->target.oid, &ref_file); |
d4a0b124 VM |
241 | } |
242 | ||
13224ea4 | 243 | git_buf_free(&ref_file); |
1a481123 | 244 | return result; |
d4a0b124 VM |
245 | } |
246 | ||
247 | static int loose_lookup_to_packfile( | |
248 | struct packref **ref_out, | |
932d1baf | 249 | git_repository *repo, |
d4a0b124 | 250 | const char *name) |
9282e921 | 251 | { |
13224ea4 | 252 | git_buf ref_file = GIT_BUF_INIT; |
d4a0b124 VM |
253 | struct packref *ref = NULL; |
254 | size_t name_len; | |
9282e921 | 255 | |
2f8a8ab2 | 256 | *ref_out = NULL; |
9282e921 | 257 | |
1a481123 VM |
258 | if (reference_read(&ref_file, NULL, repo->path_repository, name, NULL) < 0) |
259 | return -1; | |
9282e921 | 260 | |
d4a0b124 VM |
261 | name_len = strlen(name); |
262 | ref = git__malloc(sizeof(struct packref) + name_len + 1); | |
1a481123 | 263 | GITERR_CHECK_ALLOC(ref); |
1d8cc731 | 264 | |
d4a0b124 VM |
265 | memcpy(ref->name, name, name_len); |
266 | ref->name[name_len] = 0; | |
1d8cc731 | 267 | |
1a481123 VM |
268 | if (loose_parse_oid(&ref->oid, &ref_file) < 0) { |
269 | git_buf_free(&ref_file); | |
270 | free(ref); | |
271 | return -1; | |
272 | } | |
ff5873ad | 273 | |
d4a0b124 VM |
274 | ref->flags = GIT_PACKREF_WAS_LOOSE; |
275 | ||
2f8a8ab2 | 276 | *ref_out = ref; |
13224ea4 | 277 | git_buf_free(&ref_file); |
1a481123 | 278 | return 0; |
9282e921 | 279 | } |
280 | ||
d4a0b124 | 281 | static int loose_write(git_reference *ref) |
87d3acf4 | 282 | { |
b762e576 | 283 | git_filebuf file = GIT_FILEBUF_INIT; |
97769280 | 284 | git_buf ref_path = GIT_BUF_INIT; |
7341bf87 | 285 | struct stat st; |
87d3acf4 | 286 | |
1a481123 VM |
287 | if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0) |
288 | return -1; | |
289 | ||
4615f0f7 | 290 | /* Remove a possibly existing empty directory hierarchy |
291 | * which name would collide with the reference name | |
292 | */ | |
293 | if (git_path_isdir(git_buf_cstr(&ref_path)) && | |
294 | (git_futils_rmdir_r(git_buf_cstr(&ref_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) { | |
295 | git_buf_free(&ref_path); | |
296 | return -1; | |
297 | } | |
298 | ||
1a481123 VM |
299 | if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { |
300 | git_buf_free(&ref_path); | |
301 | return -1; | |
302 | } | |
87d3acf4 | 303 | |
1a481123 | 304 | git_buf_free(&ref_path); |
87d3acf4 | 305 | |
d4a0b124 | 306 | if (ref->flags & GIT_REF_OID) { |
e7e0e20f | 307 | char oid[GIT_OID_HEXSZ + 1]; |
87d3acf4 | 308 | |
d4a0b124 VM |
309 | git_oid_fmt(oid, &ref->target.oid); |
310 | oid[GIT_OID_HEXSZ] = '\0'; | |
87d3acf4 | 311 | |
1a481123 | 312 | git_filebuf_printf(&file, "%s\n", oid); |
87d3acf4 | 313 | |
1a481123 VM |
314 | } else if (ref->flags & GIT_REF_SYMBOLIC) { |
315 | git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); | |
87d3acf4 | 316 | } else { |
1a481123 | 317 | assert(0); /* don't let this happen */ |
87d3acf4 VM |
318 | } |
319 | ||
1a481123 | 320 | if (p_stat(ref_path.ptr, &st) == 0) |
7341bf87 VM |
321 | ref->mtime = st.st_mtime; |
322 | ||
d4a0b124 | 323 | return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); |
87d3acf4 VM |
324 | } |
325 | ||
87d3acf4 | 326 | static int packed_parse_peel( |
d4a0b124 | 327 | struct packref *tag_ref, |
932d1baf | 328 | const char **buffer_out, |
2f8a8ab2 | 329 | const char *buffer_end) |
9282e921 | 330 | { |
2f8a8ab2 VM |
331 | const char *buffer = *buffer_out + 1; |
332 | ||
333 | assert(buffer[-1] == '^'); | |
9282e921 | 334 | |
335 | /* Ensure it's not the first entry of the file */ | |
2f8a8ab2 | 336 | if (tag_ref == NULL) |
1a481123 | 337 | goto corrupt; |
9282e921 | 338 | |
339 | /* Ensure reference is a tag */ | |
d4a0b124 | 340 | if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) |
1a481123 | 341 | goto corrupt; |
9282e921 | 342 | |
2f8a8ab2 | 343 | if (buffer + GIT_OID_HEXSZ >= buffer_end) |
1a481123 | 344 | goto corrupt; |
9282e921 | 345 | |
2f8a8ab2 | 346 | /* Is this a valid object id? */ |
0d0fa7c3 | 347 | if (git_oid_fromstr(&tag_ref->peel, buffer) < 0) |
1a481123 | 348 | goto corrupt; |
2f8a8ab2 | 349 | |
ff5873ad VM |
350 | buffer = buffer + GIT_OID_HEXSZ; |
351 | if (*buffer == '\r') | |
352 | buffer++; | |
353 | ||
354 | if (*buffer != '\n') | |
1a481123 | 355 | goto corrupt; |
ff5873ad VM |
356 | |
357 | *buffer_out = buffer + 1; | |
1a481123 VM |
358 | return 0; |
359 | ||
360 | corrupt: | |
361 | giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); | |
362 | return -1; | |
9282e921 | 363 | } |
364 | ||
87d3acf4 | 365 | static int packed_parse_oid( |
d4a0b124 | 366 | struct packref **ref_out, |
2f8a8ab2 VM |
367 | const char **buffer_out, |
368 | const char *buffer_end) | |
9282e921 | 369 | { |
d4a0b124 | 370 | struct packref *ref = NULL; |
2f8a8ab2 VM |
371 | |
372 | const char *buffer = *buffer_out; | |
373 | const char *refname_begin, *refname_end; | |
374 | ||
d4a0b124 | 375 | size_t refname_len; |
1d8cc731 | 376 | git_oid id; |
9282e921 | 377 | |
2f8a8ab2 | 378 | refname_begin = (buffer + GIT_OID_HEXSZ + 1); |
1a481123 VM |
379 | if (refname_begin >= buffer_end || refname_begin[-1] != ' ') |
380 | goto corrupt; | |
9282e921 | 381 | |
2f8a8ab2 | 382 | /* Is this a valid object id? */ |
1a481123 VM |
383 | if (git_oid_fromstr(&id, buffer) < 0) |
384 | goto corrupt; | |
9282e921 | 385 | |
2f8a8ab2 | 386 | refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); |
1a481123 VM |
387 | if (refname_end == NULL) |
388 | goto corrupt; | |
9282e921 | 389 | |
d4a0b124 VM |
390 | if (refname_end[-1] == '\r') |
391 | refname_end--; | |
9282e921 | 392 | |
d4a0b124 | 393 | refname_len = refname_end - refname_begin; |
9282e921 | 394 | |
d4a0b124 | 395 | ref = git__malloc(sizeof(struct packref) + refname_len + 1); |
1a481123 | 396 | GITERR_CHECK_ALLOC(ref); |
1d8cc731 | 397 | |
d4a0b124 VM |
398 | memcpy(ref->name, refname_begin, refname_len); |
399 | ref->name[refname_len] = 0; | |
6c8b458d | 400 | |
86194b24 | 401 | git_oid_cpy(&ref->oid, &id); |
d4a0b124 VM |
402 | |
403 | ref->flags = 0; | |
9282e921 | 404 | |
2f8a8ab2 | 405 | *ref_out = ref; |
9282e921 | 406 | *buffer_out = refname_end + 1; |
407 | ||
1a481123 | 408 | return 0; |
2f8a8ab2 | 409 | |
1a481123 | 410 | corrupt: |
854eccbb | 411 | git__free(ref); |
1a481123 VM |
412 | giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); |
413 | return -1; | |
9282e921 | 414 | } |
415 | ||
87d3acf4 | 416 | static int packed_load(git_repository *repo) |
9282e921 | 417 | { |
1a481123 | 418 | int result, updated; |
13224ea4 | 419 | git_buf packfile = GIT_BUF_INIT; |
2f8a8ab2 | 420 | const char *buffer_start, *buffer_end; |
87d3acf4 VM |
421 | git_refcache *ref_cache = &repo->references; |
422 | ||
c4982328 CMN |
423 | /* First we make sure we have allocated the hash table */ |
424 | if (ref_cache->packfile == NULL) { | |
7341bf87 | 425 | ref_cache->packfile = git_hashtable_alloc( |
ee1f0b1a | 426 | default_table_size, git_hash__strhash_cb, git_hash__strcmp_cb); |
7341bf87 | 427 | |
1a481123 | 428 | GITERR_CHECK_ALLOC(ref_cache->packfile); |
7341bf87 VM |
429 | } |
430 | ||
1a481123 | 431 | result = reference_read(&packfile, &ref_cache->packfile_time, |
d4a0b124 | 432 | repo->path_repository, GIT_PACKEDREFS_FILE, &updated); |
87d3acf4 | 433 | |
c4982328 CMN |
434 | /* |
435 | * If we couldn't find the file, we need to clear the table and | |
436 | * return. On any other error, we return that error. If everything | |
437 | * went fine and the file wasn't updated, then there's nothing new | |
438 | * for us here, so just return. Anything else means we need to | |
439 | * refresh the packed refs. | |
440 | */ | |
1a481123 | 441 | if (result == GIT_ENOTFOUND) { |
c4982328 | 442 | git_hashtable_clear(ref_cache->packfile); |
1a481123 | 443 | return 0; |
c4982328 | 444 | } |
2f8a8ab2 | 445 | |
1a481123 VM |
446 | if (result < 0) |
447 | return -1; | |
448 | ||
449 | if (!updated) | |
450 | return 0; | |
451 | ||
c4982328 CMN |
452 | /* |
453 | * At this point, we want to refresh the packed refs. We already | |
454 | * have the contents in our buffer. | |
455 | */ | |
c4982328 | 456 | git_hashtable_clear(ref_cache->packfile); |
9282e921 | 457 | |
13224ea4 VM |
458 | buffer_start = (const char *)packfile.ptr; |
459 | buffer_end = (const char *)(buffer_start) + packfile.size; | |
9282e921 | 460 | |
7c8a7b91 VM |
461 | while (buffer_start < buffer_end && buffer_start[0] == '#') { |
462 | buffer_start = strchr(buffer_start, '\n'); | |
1a481123 VM |
463 | if (buffer_start == NULL) |
464 | goto parse_failed; | |
465 | ||
ddc9e79a | 466 | buffer_start++; |
7c8a7b91 | 467 | } |
ddc9e79a | 468 | |
9282e921 | 469 | while (buffer_start < buffer_end) { |
d4a0b124 | 470 | struct packref *ref = NULL; |
2f8a8ab2 | 471 | |
1a481123 VM |
472 | if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0) |
473 | goto parse_failed; | |
2f8a8ab2 | 474 | |
9282e921 | 475 | if (buffer_start[0] == '^') { |
1a481123 VM |
476 | if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0) |
477 | goto parse_failed; | |
2f8a8ab2 | 478 | } |
9282e921 | 479 | |
1a481123 VM |
480 | if (git_hashtable_insert(ref_cache->packfile, ref->name, ref) < 0) |
481 | return -1; | |
2f8a8ab2 | 482 | } |
9282e921 | 483 | |
13224ea4 | 484 | git_buf_free(&packfile); |
1a481123 | 485 | return 0; |
7341bf87 | 486 | |
1a481123 | 487 | parse_failed: |
7341bf87 VM |
488 | git_hashtable_free(ref_cache->packfile); |
489 | ref_cache->packfile = NULL; | |
13224ea4 | 490 | git_buf_free(&packfile); |
1a481123 | 491 | return -1; |
2f8a8ab2 | 492 | } |
9282e921 | 493 | |
00571828 | 494 | |
00571828 | 495 | struct dirent_list_data { |
7ad96e51 | 496 | git_repository *repo; |
00571828 VM |
497 | size_t repo_path_len; |
498 | unsigned int list_flags; | |
09e8de0f VM |
499 | |
500 | int (*callback)(const char *, void *); | |
501 | void *callback_payload; | |
00571828 VM |
502 | }; |
503 | ||
1a481123 | 504 | static int _dirent_loose_listall(void *_data, git_buf *full_path) |
00571828 VM |
505 | { |
506 | struct dirent_list_data *data = (struct dirent_list_data *)_data; | |
97769280 | 507 | const char *file_path = full_path->ptr + data->repo_path_len; |
00571828 | 508 | |
1a481123 | 509 | if (git_path_isdir(full_path->ptr) == true) |
1744fafe | 510 | return git_path_direach(full_path, _dirent_loose_listall, _data); |
00571828 | 511 | |
7ad96e51 | 512 | /* do not add twice a reference that exists already in the packfile */ |
b5abb881 VM |
513 | if ((data->list_flags & GIT_REF_PACKED) != 0 && |
514 | git_hashtable_lookup(data->repo->references.packfile, file_path) != NULL) | |
1a481123 | 515 | return 0; |
7ad96e51 | 516 | |
09e8de0f VM |
517 | if (data->list_flags != GIT_REF_LISTALL) { |
518 | if ((data->list_flags & loose_guess_rtype(full_path)) == 0) | |
1a481123 | 519 | return 0; /* we are filtering out this reference */ |
09e8de0f | 520 | } |
00571828 | 521 | |
09e8de0f | 522 | return data->callback(file_path, data->callback_payload); |
00571828 VM |
523 | } |
524 | ||
97769280 | 525 | static int _dirent_loose_load(void *data, git_buf *full_path) |
2f8a8ab2 | 526 | { |
87d3acf4 | 527 | git_repository *repository = (git_repository *)data; |
6c8b458d | 528 | void *old_ref = NULL; |
d4a0b124 | 529 | struct packref *ref; |
97769280 | 530 | const char *file_path; |
86194b24 | 531 | |
1a481123 | 532 | if (git_path_isdir(full_path->ptr) == true) |
1744fafe | 533 | return git_path_direach(full_path, _dirent_loose_load, repository); |
9282e921 | 534 | |
97769280 | 535 | file_path = full_path->ptr + strlen(repository->path_repository); |
d4a0b124 | 536 | |
1a481123 VM |
537 | if (loose_lookup_to_packfile(&ref, repository, file_path) < 0) |
538 | return -1; | |
87d3acf4 | 539 | |
1a481123 VM |
540 | if (git_hashtable_insert2(repository->references.packfile, |
541 | ref->name, ref, &old_ref) < 0) { | |
542 | git__free(ref); | |
543 | return -1; | |
87d3acf4 | 544 | } |
9282e921 | 545 | |
1a481123 VM |
546 | git__free(old_ref); |
547 | return 0; | |
2f8a8ab2 VM |
548 | } |
549 | ||
87d3acf4 VM |
550 | /* |
551 | * Load all the loose references from the repository | |
552 | * into the in-memory Packfile, and build a vector with | |
553 | * all the references so it can be written back to | |
554 | * disk. | |
555 | */ | |
556 | static int packed_loadloose(git_repository *repository) | |
2f8a8ab2 | 557 | { |
97769280 | 558 | git_buf refs_path = GIT_BUF_INIT; |
1a481123 | 559 | int result; |
86194b24 | 560 | |
87d3acf4 VM |
561 | /* the packfile must have been previously loaded! */ |
562 | assert(repository->references.packfile); | |
9282e921 | 563 | |
1a481123 VM |
564 | if (git_buf_joinpath(&refs_path, repository->path_repository, GIT_REFS_DIR) < 0) |
565 | return -1; | |
86194b24 | 566 | |
87d3acf4 VM |
567 | /* |
568 | * Load all the loose files from disk into the Packfile table. | |
569 | * This will overwrite any old packed entries with their | |
932d1baf | 570 | * updated loose versions |
87d3acf4 | 571 | */ |
1a481123 | 572 | result = git_path_direach(&refs_path, _dirent_loose_load, repository); |
97769280 | 573 | git_buf_free(&refs_path); |
1a481123 VM |
574 | |
575 | return result; | |
2f8a8ab2 | 576 | } |
9282e921 | 577 | |
87d3acf4 VM |
578 | /* |
579 | * Write a single reference into a packfile | |
580 | */ | |
d4a0b124 | 581 | static int packed_write_ref(struct packref *ref, git_filebuf *file) |
2f8a8ab2 | 582 | { |
87d3acf4 | 583 | char oid[GIT_OID_HEXSZ + 1]; |
2f8a8ab2 | 584 | |
87d3acf4 VM |
585 | git_oid_fmt(oid, &ref->oid); |
586 | oid[GIT_OID_HEXSZ] = 0; | |
2f8a8ab2 | 587 | |
932d1baf | 588 | /* |
87d3acf4 VM |
589 | * For references that peel to an object in the repo, we must |
590 | * write the resulting peel on a separate line, e.g. | |
591 | * | |
592 | * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4 | |
593 | * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100 | |
594 | * | |
595 | * This obviously only applies to tags. | |
596 | * The required peels have already been loaded into `ref->peel_target`. | |
597 | */ | |
d4a0b124 | 598 | if (ref->flags & GIT_PACKREF_HAS_PEEL) { |
87d3acf4 | 599 | char peel[GIT_OID_HEXSZ + 1]; |
d4a0b124 | 600 | git_oid_fmt(peel, &ref->peel); |
87d3acf4 VM |
601 | peel[GIT_OID_HEXSZ] = 0; |
602 | ||
1a481123 VM |
603 | if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) |
604 | return -1; | |
87d3acf4 | 605 | } else { |
1a481123 VM |
606 | if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) |
607 | return -1; | |
87d3acf4 VM |
608 | } |
609 | ||
1a481123 | 610 | return 0; |
9282e921 | 611 | } |
612 | ||
87d3acf4 VM |
613 | /* |
614 | * Find out what object this reference resolves to. | |
615 | * | |
932d1baf | 616 | * For references that point to a 'big' tag (e.g. an |
87d3acf4 VM |
617 | * actual tag object on the repository), we need to |
618 | * cache on the packfile the OID of the object to | |
619 | * which that 'big tag' is pointing to. | |
620 | */ | |
d4a0b124 | 621 | static int packed_find_peel(git_repository *repo, struct packref *ref) |
9282e921 | 622 | { |
d79f1da6 | 623 | git_object *object; |
32054c24 | 624 | |
d4a0b124 | 625 | if (ref->flags & GIT_PACKREF_HAS_PEEL) |
1a481123 | 626 | return 0; |
9282e921 | 627 | |
87d3acf4 VM |
628 | /* |
629 | * Only applies to tags, i.e. references | |
630 | * in the /refs/tags folder | |
631 | */ | |
d4a0b124 | 632 | if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0) |
1a481123 | 633 | return 0; |
9282e921 | 634 | |
87d3acf4 | 635 | /* |
d79f1da6 | 636 | * Find the tagged object in the repository |
87d3acf4 | 637 | */ |
1a481123 VM |
638 | if (git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY) < 0) |
639 | return -1; | |
86194b24 | 640 | |
87d3acf4 | 641 | /* |
d79f1da6 VM |
642 | * If the tagged object is a Tag object, we need to resolve it; |
643 | * if the ref is actually a 'weak' ref, we don't need to resolve | |
644 | * anything. | |
87d3acf4 | 645 | */ |
d79f1da6 VM |
646 | if (git_object_type(object) == GIT_OBJ_TAG) { |
647 | git_tag *tag = (git_tag *)object; | |
86194b24 | 648 | |
d79f1da6 VM |
649 | /* |
650 | * Find the object pointed at by this tag | |
651 | */ | |
d4a0b124 VM |
652 | git_oid_cpy(&ref->peel, git_tag_target_oid(tag)); |
653 | ref->flags |= GIT_PACKREF_HAS_PEEL; | |
d79f1da6 VM |
654 | |
655 | /* | |
656 | * The reference has now cached the resolved OID, and is | |
657 | * marked at such. When written to the packfile, it'll be | |
658 | * accompanied by this resolved oid | |
659 | */ | |
660 | } | |
87d3acf4 | 661 | |
45e79e37 | 662 | git_object_free(object); |
1a481123 | 663 | return 0; |
2f8a8ab2 | 664 | } |
9282e921 | 665 | |
87d3acf4 VM |
666 | /* |
667 | * Remove all loose references | |
668 | * | |
669 | * Once we have successfully written a packfile, | |
670 | * all the loose references that were packed must be | |
671 | * removed from disk. | |
672 | * | |
673 | * This is a dangerous method; make sure the packfile | |
674 | * is well-written, because we are destructing references | |
675 | * here otherwise. | |
676 | */ | |
677 | static int packed_remove_loose(git_repository *repo, git_vector *packing_list) | |
2f8a8ab2 | 678 | { |
87d3acf4 | 679 | unsigned int i; |
97769280 | 680 | git_buf full_path = GIT_BUF_INIT; |
1a481123 | 681 | int failed = 0; |
87d3acf4 VM |
682 | |
683 | for (i = 0; i < packing_list->length; ++i) { | |
d4a0b124 | 684 | struct packref *ref = git_vector_get(packing_list, i); |
8f90ced5 | 685 | |
d4a0b124 | 686 | if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0) |
8f90ced5 | 687 | continue; |
688 | ||
1a481123 VM |
689 | if (git_buf_joinpath(&full_path, repo->path_repository, ref->name) < 0) |
690 | return -1; /* critical; do not try to recover on oom */ | |
97769280 | 691 | |
1a481123 VM |
692 | if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) { |
693 | if (failed) | |
694 | continue; | |
87d3acf4 | 695 | |
1a481123 VM |
696 | giterr_set(GITERR_REFERENCE, |
697 | "Failed to remove loose reference '%s' after packing: %s", | |
698 | full_path.ptr, strerror(errno)); | |
699 | ||
700 | failed = 1; | |
701 | } | |
87d3acf4 VM |
702 | |
703 | /* | |
704 | * if we fail to remove a single file, this is *not* good, | |
705 | * but we should keep going and remove as many as possible. | |
706 | * After we've removed as many files as possible, we return | |
707 | * the error code anyway. | |
87d3acf4 VM |
708 | */ |
709 | } | |
710 | ||
97769280 | 711 | git_buf_free(&full_path); |
1a481123 | 712 | return failed ? -1 : 0; |
2f8a8ab2 | 713 | } |
9282e921 | 714 | |
87d3acf4 | 715 | static int packed_sort(const void *a, const void *b) |
2f8a8ab2 | 716 | { |
d4a0b124 VM |
717 | const struct packref *ref_a = (const struct packref *)a; |
718 | const struct packref *ref_b = (const struct packref *)b; | |
87d3acf4 VM |
719 | |
720 | return strcmp(ref_a->name, ref_b->name); | |
2f8a8ab2 VM |
721 | } |
722 | ||
87d3acf4 VM |
723 | /* |
724 | * Write all the contents in the in-memory packfile to disk. | |
725 | */ | |
726 | static int packed_write(git_repository *repo) | |
2f8a8ab2 | 727 | { |
b762e576 | 728 | git_filebuf pack_file = GIT_FILEBUF_INIT; |
87d3acf4 | 729 | unsigned int i; |
97769280 | 730 | git_buf pack_file_path = GIT_BUF_INIT; |
87d3acf4 VM |
731 | git_vector packing_list; |
732 | size_t total_refs; | |
2f8a8ab2 | 733 | |
87d3acf4 | 734 | assert(repo && repo->references.packfile); |
9282e921 | 735 | |
87d3acf4 | 736 | total_refs = repo->references.packfile->key_count; |
1a481123 VM |
737 | |
738 | if (git_vector_init(&packing_list, total_refs, packed_sort) < 0) | |
739 | return -1; | |
9282e921 | 740 | |
87d3acf4 VM |
741 | /* Load all the packfile into a vector */ |
742 | { | |
d4a0b124 | 743 | struct packref *reference; |
87d3acf4 | 744 | |
854eccbb | 745 | GIT_HASHTABLE_FOREACH_VALUE(repo->references.packfile, reference, |
d4a0b124 VM |
746 | /* cannot fail: vector already has the right size */ |
747 | git_vector_insert(&packing_list, reference); | |
87d3acf4 | 748 | ); |
9282e921 | 749 | } |
750 | ||
87d3acf4 VM |
751 | /* sort the vector so the entries appear sorted on the packfile */ |
752 | git_vector_sort(&packing_list); | |
9282e921 | 753 | |
87d3acf4 | 754 | /* Now we can open the file! */ |
1a481123 VM |
755 | if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0) |
756 | goto cleanup_memory; | |
97769280 | 757 | |
1a481123 VM |
758 | if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0) |
759 | goto cleanup_packfile; | |
9282e921 | 760 | |
7c8a7b91 VM |
761 | /* Packfiles have a header... apparently |
762 | * This is in fact not required, but we might as well print it | |
763 | * just for kicks */ | |
1a481123 VM |
764 | if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) |
765 | goto cleanup_packfile; | |
2f8a8ab2 | 766 | |
87d3acf4 | 767 | for (i = 0; i < packing_list.length; ++i) { |
d4a0b124 | 768 | struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); |
2f8a8ab2 | 769 | |
1a481123 VM |
770 | if (packed_find_peel(repo, ref) < 0) |
771 | goto cleanup_packfile; | |
2b397327 | 772 | |
1a481123 VM |
773 | if (packed_write_ref(ref, &pack_file) < 0) |
774 | goto cleanup_packfile; | |
87d3acf4 | 775 | } |
2f8a8ab2 | 776 | |
87d3acf4 VM |
777 | /* if we've written all the references properly, we can commit |
778 | * the packfile to make the changes effective */ | |
1a481123 VM |
779 | if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0) |
780 | goto cleanup_memory; | |
7341bf87 | 781 | |
1a481123 VM |
782 | /* when and only when the packfile has been properly written, |
783 | * we can go ahead and remove the loose refs */ | |
784 | if (packed_remove_loose(repo, &packing_list) < 0) | |
785 | goto cleanup_memory; | |
7341bf87 | 786 | |
1a481123 VM |
787 | { |
788 | struct stat st; | |
789 | if (p_stat(pack_file_path.ptr, &st) == 0) | |
790 | repo->references.packfile_time = st.st_mtime; | |
791 | } | |
9282e921 | 792 | |
87d3acf4 | 793 | git_vector_free(&packing_list); |
97769280 | 794 | git_buf_free(&pack_file_path); |
1d8cc731 | 795 | |
1a481123 VM |
796 | /* we're good now */ |
797 | return 0; | |
798 | ||
799 | cleanup_packfile: | |
800 | git_filebuf_cleanup(&pack_file); | |
97769280 | 801 | |
1a481123 VM |
802 | cleanup_memory: |
803 | git_vector_free(&packing_list); | |
804 | git_buf_free(&pack_file_path); | |
805 | ||
806 | return -1; | |
87d3acf4 | 807 | } |
2f8a8ab2 | 808 | |
45d387ac VM |
809 | struct reference_available_t { |
810 | const char *new_ref; | |
811 | const char *old_ref; | |
812 | int available; | |
813 | }; | |
814 | ||
1b6d8163 MS |
815 | static int _reference_available_cb(const char *ref, void *data) |
816 | { | |
45d387ac | 817 | struct reference_available_t *d; |
f120e92b | 818 | |
1b6d8163 | 819 | assert(ref && data); |
1a481123 | 820 | d = (struct reference_available_t *)data; |
1b6d8163 | 821 | |
45d387ac | 822 | if (!d->old_ref || strcmp(d->old_ref, ref)) { |
1b6d8163 | 823 | int reflen = strlen(ref); |
45d387ac | 824 | int newlen = strlen(d->new_ref); |
1b6d8163 | 825 | int cmplen = reflen < newlen ? reflen : newlen; |
45d387ac | 826 | const char *lead = reflen < newlen ? d->new_ref : ref; |
1b6d8163 | 827 | |
45d387ac VM |
828 | if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') { |
829 | d->available = 0; | |
830 | return -1; | |
831 | } | |
1b6d8163 MS |
832 | } |
833 | ||
45d387ac | 834 | return 0; |
1b6d8163 MS |
835 | } |
836 | ||
1a481123 | 837 | static int reference_path_available( |
d4a0b124 VM |
838 | git_repository *repo, |
839 | const char *ref, | |
1a481123 | 840 | const char* old_ref) |
1b6d8163 | 841 | { |
45d387ac | 842 | struct reference_available_t data; |
1b6d8163 | 843 | |
45d387ac VM |
844 | data.new_ref = ref; |
845 | data.old_ref = old_ref; | |
846 | data.available = 1; | |
1b6d8163 | 847 | |
d4a0b124 | 848 | if (git_reference_foreach(repo, GIT_REF_LISTALL, |
1a481123 VM |
849 | _reference_available_cb, (void *)&data) < 0) |
850 | return -1; | |
851 | ||
852 | if (!data.available) { | |
853 | giterr_set(GITERR_REFERENCE, | |
e1de726c | 854 | "The path to reference '%s' collides with an existing one", ref); |
45d387ac | 855 | return -1; |
1a481123 | 856 | } |
1b6d8163 | 857 | |
45d387ac | 858 | return 0; |
1b6d8163 MS |
859 | } |
860 | ||
d4a0b124 | 861 | static int reference_exists(int *exists, git_repository *repo, const char *ref_name) |
2f8a8ab2 | 862 | { |
97769280 | 863 | git_buf ref_path = GIT_BUF_INIT; |
2f8a8ab2 | 864 | |
1a481123 VM |
865 | if (packed_load(repo) < 0) |
866 | return -1; | |
2f8a8ab2 | 867 | |
1a481123 VM |
868 | if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0) |
869 | return -1; | |
2f8a8ab2 | 870 | |
1a481123 | 871 | if (git_path_isfile(ref_path.ptr) == true || |
97769280 | 872 | git_hashtable_lookup(repo->references.packfile, ref_path.ptr) != NULL) { |
d4a0b124 VM |
873 | *exists = 1; |
874 | } else { | |
875 | *exists = 0; | |
876 | } | |
2f8a8ab2 | 877 | |
97769280 | 878 | git_buf_free(&ref_path); |
1a481123 VM |
879 | return 0; |
880 | } | |
97769280 | 881 | |
1a481123 VM |
882 | /* |
883 | * Check if a reference could be written to disk, based on: | |
884 | * | |
885 | * - Whether a reference with the same name already exists, | |
886 | * and we are allowing or disallowing overwrites | |
887 | * | |
888 | * - Whether the name of the reference would collide with | |
889 | * an existing path | |
890 | */ | |
891 | static int reference_can_write( | |
892 | git_repository *repo, | |
893 | const char *refname, | |
894 | const char *previous_name, | |
895 | int force) | |
896 | { | |
897 | /* see if the reference shares a path with an existing reference; | |
898 | * if a path is shared, we cannot create the reference, even when forcing */ | |
899 | if (reference_path_available(repo, refname, previous_name) < 0) | |
900 | return -1; | |
901 | ||
902 | /* check if the reference actually exists, but only if we are not forcing | |
903 | * the rename. If we are forcing, it's OK to overwrite */ | |
904 | if (!force) { | |
905 | int exists; | |
906 | ||
907 | if (reference_exists(&exists, repo, refname) < 0) | |
908 | return -1; | |
909 | ||
910 | /* We cannot proceed if the reference already exists and we're not forcing | |
911 | * the rename; the existing one would be overwritten */ | |
912 | if (exists) { | |
913 | giterr_set(GITERR_REFERENCE, | |
e1de726c | 914 | "A reference with that name (%s) already exists", refname); |
1a481123 VM |
915 | return GIT_EEXISTS; |
916 | } | |
917 | } | |
918 | ||
919 | /* FIXME: if the reference exists and we are forcing, do we really need to | |
920 | * remove the reference first? | |
921 | * | |
922 | * Two cases: | |
923 | * | |
924 | * - the reference already exists and is loose: not a problem, the file | |
925 | * gets overwritten on disk | |
926 | * | |
927 | * - the reference already exists and is packed: we write a new one as | |
928 | * loose, which by all means renders the packed one useless | |
929 | */ | |
930 | ||
931 | return 0; | |
d4a0b124 | 932 | } |
2f8a8ab2 | 933 | |
1a481123 | 934 | |
d4a0b124 VM |
935 | static int packed_lookup(git_reference *ref) |
936 | { | |
d4a0b124 | 937 | struct packref *pack_ref = NULL; |
9282e921 | 938 | |
1a481123 VM |
939 | if (packed_load(ref->owner) < 0) |
940 | return -1; | |
9282e921 | 941 | |
1a481123 VM |
942 | /* maybe the packfile hasn't changed at all, so we don't |
943 | * have to re-lookup the reference */ | |
944 | if ((ref->flags & GIT_REF_PACKED) && | |
d4a0b124 | 945 | ref->mtime == ref->owner->references.packfile_time) |
1a481123 | 946 | return 0; |
86194b24 | 947 | |
fa515656 | 948 | if (ref->flags & GIT_REF_SYMBOLIC) { |
854eccbb | 949 | git__free(ref->target.symbolic); |
fa515656 VM |
950 | ref->target.symbolic = NULL; |
951 | } | |
d4a0b124 VM |
952 | |
953 | /* Look up on the packfile */ | |
954 | pack_ref = git_hashtable_lookup(ref->owner->references.packfile, ref->name); | |
1a481123 VM |
955 | if (pack_ref == NULL) { |
956 | giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref->name); | |
957 | return GIT_ENOTFOUND; | |
958 | } | |
d4a0b124 VM |
959 | |
960 | ref->flags = GIT_REF_OID | GIT_REF_PACKED; | |
961 | ref->mtime = ref->owner->references.packfile_time; | |
962 | git_oid_cpy(&ref->target.oid, &pack_ref->oid); | |
963 | ||
1a481123 | 964 | return 0; |
2f8a8ab2 | 965 | } |
9282e921 | 966 | |
1a481123 | 967 | static int reference_lookup(git_reference *ref) |
a46ec457 | 968 | { |
45d387ac | 969 | int result; |
a46ec457 | 970 | |
1a481123 VM |
971 | result = loose_lookup(ref); |
972 | if (result == 0) | |
973 | return 0; | |
a46ec457 | 974 | |
1a481123 VM |
975 | /* only try to lookup this reference on the packfile if it |
976 | * wasn't found on the loose refs; not if there was a critical error */ | |
977 | if (result == GIT_ENOTFOUND) { | |
978 | giterr_clear(); | |
979 | result = packed_lookup(ref); | |
980 | if (result == 0) | |
981 | return 0; | |
982 | } | |
a46ec457 | 983 | |
1a481123 | 984 | /* unexpected error; free the reference */ |
d4a0b124 | 985 | git_reference_free(ref); |
1a481123 | 986 | return result; |
a46ec457 MS |
987 | } |
988 | ||
d4a0b124 VM |
989 | /* |
990 | * Delete a reference. | |
991 | * This is an internal method; the reference is removed | |
992 | * from disk or the packfile, but the pointer is not freed | |
87d3acf4 | 993 | */ |
1a481123 | 994 | static int reference_delete(git_reference *ref) |
87d3acf4 | 995 | { |
45d387ac | 996 | int result; |
d4a0b124 | 997 | |
87d3acf4 VM |
998 | assert(ref); |
999 | ||
d4a0b124 VM |
1000 | /* If the reference is packed, this is an expensive operation. |
1001 | * We need to reload the packfile, remove the reference from the | |
1002 | * packing list, and repack */ | |
1003 | if (ref->flags & GIT_REF_PACKED) { | |
20c50b9e | 1004 | struct packref *packref; |
d4a0b124 | 1005 | /* load the existing packfile */ |
1a481123 | 1006 | if (packed_load(ref->owner) < 0) |
45d387ac | 1007 | return -1; |
87d3acf4 | 1008 | |
20c50b9e | 1009 | if (git_hashtable_remove2(ref->owner->references.packfile, |
45d387ac | 1010 | ref->name, (void **) &packref) < 0) { |
1a481123 | 1011 | giterr_set(GITERR_REFERENCE, |
45d387ac VM |
1012 | "Reference %s stopped existing in the packfile", ref->name); |
1013 | return -1; | |
1014 | } | |
87d3acf4 | 1015 | |
45d387ac | 1016 | git__free(packref); |
1a481123 | 1017 | if (packed_write(ref->owner) < 0) |
45d387ac | 1018 | return -1; |
87d3acf4 | 1019 | |
d4a0b124 VM |
1020 | /* If the reference is loose, we can just remove the reference |
1021 | * from the filesystem */ | |
1022 | } else { | |
d4a0b124 | 1023 | git_reference *ref_in_pack; |
97769280 | 1024 | git_buf full_path = GIT_BUF_INIT; |
a46ec457 | 1025 | |
1a481123 | 1026 | if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0) |
45d387ac | 1027 | return -1; |
a46ec457 | 1028 | |
45d387ac | 1029 | result = p_unlink(full_path.ptr); |
97769280 | 1030 | git_buf_free(&full_path); /* done with path at this point */ |
45d387ac VM |
1031 | |
1032 | if (result < 0) { | |
ae9e29fd | 1033 | giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr); |
45d387ac VM |
1034 | return -1; |
1035 | } | |
a46ec457 | 1036 | |
d4a0b124 VM |
1037 | /* When deleting a loose reference, we have to ensure that an older |
1038 | * packed version of it doesn't exist */ | |
1a481123 | 1039 | if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name) == 0) { |
d4a0b124 | 1040 | assert((ref_in_pack->flags & GIT_REF_PACKED) != 0); |
1a481123 | 1041 | return git_reference_delete(ref_in_pack); |
d4a0b124 | 1042 | } |
1a481123 VM |
1043 | |
1044 | giterr_clear(); | |
d4a0b124 VM |
1045 | } |
1046 | ||
45d387ac | 1047 | return 0; |
a46ec457 MS |
1048 | } |
1049 | ||
1a481123 | 1050 | int git_reference_delete(git_reference *ref) |
a46ec457 | 1051 | { |
1a481123 | 1052 | int result = reference_delete(ref); |
d4a0b124 | 1053 | git_reference_free(ref); |
45d387ac | 1054 | return result; |
a46ec457 MS |
1055 | } |
1056 | ||
d4a0b124 | 1057 | int git_reference_lookup(git_reference **ref_out, |
1a481123 | 1058 | git_repository *repo, const char *name) |
a46ec457 | 1059 | { |
d4a0b124 VM |
1060 | char normalized_name[GIT_REFNAME_MAX]; |
1061 | git_reference *ref = NULL; | |
1a481123 | 1062 | int result; |
d4a0b124 VM |
1063 | |
1064 | assert(ref_out && repo && name); | |
1a481123 | 1065 | *ref_out = NULL; |
d4a0b124 | 1066 | |
1a481123 | 1067 | if (normalize_name(normalized_name, sizeof(normalized_name), name, 0) < 0) |
45d387ac | 1068 | return -1; |
a46ec457 | 1069 | |
1a481123 | 1070 | if (reference_alloc(&ref, repo, normalized_name) < 0) |
45d387ac | 1071 | return -1; |
d4a0b124 | 1072 | |
1a481123 VM |
1073 | result = reference_lookup(ref); |
1074 | if (result == 0) | |
1075 | *ref_out = ref; | |
1076 | ||
1077 | return result; | |
a46ec457 MS |
1078 | } |
1079 | ||
d4a0b124 VM |
1080 | /** |
1081 | * Getters | |
1082 | */ | |
1083 | git_rtype git_reference_type(git_reference *ref) | |
87d3acf4 VM |
1084 | { |
1085 | assert(ref); | |
d4a0b124 VM |
1086 | |
1087 | if (ref->flags & GIT_REF_OID) | |
1088 | return GIT_REF_OID; | |
1089 | ||
1090 | if (ref->flags & GIT_REF_SYMBOLIC) | |
1091 | return GIT_REF_SYMBOLIC; | |
1092 | ||
1093 | return GIT_REF_INVALID; | |
87d3acf4 VM |
1094 | } |
1095 | ||
d4a0b124 | 1096 | int git_reference_is_packed(git_reference *ref) |
87d3acf4 VM |
1097 | { |
1098 | assert(ref); | |
d4a0b124 | 1099 | return !!(ref->flags & GIT_REF_PACKED); |
87d3acf4 VM |
1100 | } |
1101 | ||
d4a0b124 | 1102 | const char *git_reference_name(git_reference *ref) |
87d3acf4 VM |
1103 | { |
1104 | assert(ref); | |
d4a0b124 | 1105 | return ref->name; |
87d3acf4 VM |
1106 | } |
1107 | ||
d4a0b124 | 1108 | git_repository *git_reference_owner(git_reference *ref) |
a46ec457 | 1109 | { |
d4a0b124 VM |
1110 | assert(ref); |
1111 | return ref->owner; | |
a46ec457 MS |
1112 | } |
1113 | ||
d4a0b124 | 1114 | const git_oid *git_reference_oid(git_reference *ref) |
87d3acf4 VM |
1115 | { |
1116 | assert(ref); | |
1117 | ||
d4a0b124 | 1118 | if ((ref->flags & GIT_REF_OID) == 0) |
87d3acf4 VM |
1119 | return NULL; |
1120 | ||
d4a0b124 | 1121 | return &ref->target.oid; |
87d3acf4 VM |
1122 | } |
1123 | ||
d4a0b124 | 1124 | const char *git_reference_target(git_reference *ref) |
a46ec457 | 1125 | { |
d4a0b124 | 1126 | assert(ref); |
a46ec457 | 1127 | |
d4a0b124 | 1128 | if ((ref->flags & GIT_REF_SYMBOLIC) == 0) |
a46ec457 MS |
1129 | return NULL; |
1130 | ||
d4a0b124 | 1131 | return ref->target.symbolic; |
a46ec457 MS |
1132 | } |
1133 | ||
d4a0b124 VM |
1134 | int git_reference_create_symbolic( |
1135 | git_reference **ref_out, | |
1136 | git_repository *repo, | |
1137 | const char *name, | |
1138 | const char *target, | |
1a481123 | 1139 | int force) |
d5afc039 VM |
1140 | { |
1141 | char normalized[GIT_REFNAME_MAX]; | |
d4a0b124 | 1142 | git_reference *ref = NULL; |
d5afc039 | 1143 | |
1a481123 | 1144 | if (normalize_name(normalized, sizeof(normalized), name, 0) < 0) |
45d387ac | 1145 | return -1; |
d5afc039 | 1146 | |
1a481123 | 1147 | if (reference_can_write(repo, normalized, NULL, force) < 0) |
45d387ac | 1148 | return -1; |
d5afc039 | 1149 | |
1a481123 | 1150 | if (reference_alloc(&ref, repo, normalized) < 0) |
45d387ac | 1151 | return -1; |
d5afc039 | 1152 | |
d4a0b124 VM |
1153 | ref->flags |= GIT_REF_SYMBOLIC; |
1154 | ||
1155 | /* set the target; this will normalize the name automatically | |
1156 | * and write the reference on disk */ | |
1a481123 | 1157 | if (git_reference_set_target(ref, target) < 0) { |
45d387ac VM |
1158 | git_reference_free(ref); |
1159 | return -1; | |
1160 | } | |
d4a0b124 VM |
1161 | if (ref_out == NULL) { |
1162 | git_reference_free(ref); | |
1163 | } else { | |
1164 | *ref_out = ref; | |
d5afc039 VM |
1165 | } |
1166 | ||
45d387ac | 1167 | return 0; |
d5afc039 VM |
1168 | } |
1169 | ||
d4a0b124 VM |
1170 | int git_reference_create_oid( |
1171 | git_reference **ref_out, | |
1172 | git_repository *repo, | |
1173 | const char *name, | |
1174 | const git_oid *id, | |
1a481123 | 1175 | int force) |
a46ec457 | 1176 | { |
d4a0b124 VM |
1177 | git_reference *ref = NULL; |
1178 | char normalized[GIT_REFNAME_MAX]; | |
a46ec457 | 1179 | |
1a481123 | 1180 | if (normalize_name(normalized, sizeof(normalized), name, 1) < 0) |
45d387ac | 1181 | return -1; |
d5afc039 | 1182 | |
1a481123 | 1183 | if (reference_can_write(repo, normalized, NULL, force) < 0) |
45d387ac | 1184 | return -1; |
d5afc039 | 1185 | |
1a481123 | 1186 | if (reference_alloc(&ref, repo, name) < 0) |
45d387ac | 1187 | return -1; |
d4a0b124 VM |
1188 | |
1189 | ref->flags |= GIT_REF_OID; | |
d5afc039 VM |
1190 | |
1191 | /* set the oid; this will write the reference on disk */ | |
1a481123 | 1192 | if (git_reference_set_oid(ref, id) < 0) { |
45d387ac VM |
1193 | git_reference_free(ref); |
1194 | return -1; | |
1195 | } | |
d5afc039 | 1196 | |
d4a0b124 VM |
1197 | if (ref_out == NULL) { |
1198 | git_reference_free(ref); | |
1199 | } else { | |
1200 | *ref_out = ref; | |
d5afc039 VM |
1201 | } |
1202 | ||
45d387ac | 1203 | return 0; |
a46ec457 | 1204 | } |
87d3acf4 VM |
1205 | /* |
1206 | * Change the OID target of a reference. | |
1207 | * | |
d4a0b124 VM |
1208 | * For both loose and packed references, just change |
1209 | * the oid in memory and (over)write the file in disk. | |
87d3acf4 | 1210 | * |
d4a0b124 VM |
1211 | * We do not repack packed references because of performance |
1212 | * reasons. | |
87d3acf4 | 1213 | */ |
1a481123 | 1214 | int git_reference_set_oid(git_reference *ref, const git_oid *id) |
87d3acf4 | 1215 | { |
9462c471 | 1216 | git_odb *odb = NULL; |
87d3acf4 | 1217 | |
45d387ac | 1218 | if ((ref->flags & GIT_REF_OID) == 0) { |
1a481123 | 1219 | giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference"); |
45d387ac VM |
1220 | return -1; |
1221 | } | |
87d3acf4 | 1222 | |
9a53df7e VM |
1223 | assert(ref->owner); |
1224 | ||
1a481123 | 1225 | if (git_repository_odb__weakptr(&odb, ref->owner) < 0) |
45d387ac | 1226 | return -1; |
9462c471 | 1227 | |
9a53df7e VM |
1228 | /* Don't let the user create references to OIDs that |
1229 | * don't exist in the ODB */ | |
45d387ac | 1230 | if (!git_odb_exists(odb, id)) { |
1a481123 | 1231 | giterr_set(GITERR_REFERENCE, |
45d387ac VM |
1232 | "Target OID for the reference doesn't exist on the repository"); |
1233 | return -1; | |
1234 | } | |
87d3acf4 | 1235 | |
d4a0b124 VM |
1236 | /* Update the OID value on `ref` */ |
1237 | git_oid_cpy(&ref->target.oid, id); | |
87d3acf4 | 1238 | |
9462c471 | 1239 | /* Write back to disk */ |
1a481123 | 1240 | return loose_write(ref); |
a46ec457 MS |
1241 | } |
1242 | ||
87d3acf4 VM |
1243 | /* |
1244 | * Change the target of a symbolic reference. | |
1245 | * | |
1246 | * This is easy because symrefs cannot be inside | |
1247 | * a pack. We just change the target in memory | |
1248 | * and overwrite the file on disk. | |
1249 | */ | |
1a481123 | 1250 | int git_reference_set_target(git_reference *ref, const char *target) |
87d3acf4 | 1251 | { |
d4a0b124 | 1252 | char normalized[GIT_REFNAME_MAX]; |
87d3acf4 | 1253 | |
45d387ac | 1254 | if ((ref->flags & GIT_REF_SYMBOLIC) == 0) { |
1a481123 | 1255 | giterr_set(GITERR_REFERENCE, |
45d387ac VM |
1256 | "Cannot set symbolic target on a direct reference"); |
1257 | return -1; | |
1258 | } | |
87d3acf4 | 1259 | |
1a481123 | 1260 | if (normalize_name(normalized, sizeof(normalized), target, 0)) |
45d387ac | 1261 | return -1; |
87d3acf4 | 1262 | |
d4a0b124 VM |
1263 | git__free(ref->target.symbolic); |
1264 | ref->target.symbolic = git__strdup(normalized); | |
1a481123 | 1265 | GITERR_CHECK_ALLOC(ref->target.symbolic); |
87d3acf4 | 1266 | |
1a481123 | 1267 | return loose_write(ref); |
87d3acf4 VM |
1268 | } |
1269 | ||
1a481123 | 1270 | int git_reference_rename(git_reference *ref, const char *new_name, int force) |
7376ad99 | 1271 | { |
1a481123 | 1272 | int result; |
97769280 | 1273 | git_buf aux_path = GIT_BUF_INIT; |
0ffcf78a | 1274 | char normalized[GIT_REFNAME_MAX]; |
858dba58 | 1275 | |
0ffcf78a | 1276 | const char *head_target = NULL; |
1a481123 | 1277 | git_reference *head = NULL; |
7376ad99 | 1278 | |
45d387ac | 1279 | if (normalize_name(normalized, sizeof(normalized), |
1a481123 | 1280 | new_name, ref->flags & GIT_REF_OID) < 0) |
45d387ac | 1281 | return -1; |
7376ad99 | 1282 | |
1a481123 | 1283 | if (reference_can_write(ref->owner, normalized, ref->name, force) < 0) |
45d387ac | 1284 | return -1; |
ca6f203c | 1285 | |
97769280 | 1286 | /* Initialize path now so we won't get an allocation failure once |
1a481123 VM |
1287 | * we actually start removing things. */ |
1288 | if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0) | |
45d387ac | 1289 | return -1; |
97769280 | 1290 | |
0ffcf78a MS |
1291 | /* |
1292 | * Now delete the old ref and remove an possibly existing directory | |
d4a0b124 VM |
1293 | * named `new_name`. Note that using the internal `reference_delete` |
1294 | * method deletes the ref from disk but doesn't free the pointer, so | |
1295 | * we can still access the ref's attributes for creating the new one | |
0ffcf78a | 1296 | */ |
1a481123 | 1297 | if (reference_delete(ref) < 0) |
549bbd13 | 1298 | goto cleanup; |
7376ad99 | 1299 | |
0ffcf78a MS |
1300 | /* |
1301 | * Finally we can create the new reference. | |
1302 | */ | |
d4a0b124 | 1303 | if (ref->flags & GIT_REF_SYMBOLIC) { |
45d387ac | 1304 | result = git_reference_create_symbolic( |
1a481123 | 1305 | NULL, ref->owner, new_name, ref->target.symbolic, force); |
0ffcf78a | 1306 | } else { |
45d387ac | 1307 | result = git_reference_create_oid( |
1a481123 | 1308 | NULL, ref->owner, new_name, &ref->target.oid, force); |
0ffcf78a | 1309 | } |
7376ad99 | 1310 | |
45d387ac | 1311 | if (result < 0) |
64093ce5 | 1312 | goto rollback; |
7376ad99 | 1313 | |
0ffcf78a MS |
1314 | /* |
1315 | * Check if we have to update HEAD. | |
1316 | */ | |
1a481123 VM |
1317 | if (git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE) < 0) { |
1318 | giterr_set(GITERR_REFERENCE, | |
45d387ac | 1319 | "Failed to update HEAD after renaming reference"); |
0ffcf78a | 1320 | goto cleanup; |
45d387ac | 1321 | } |
7376ad99 | 1322 | |
d4a0b124 | 1323 | head_target = git_reference_target(head); |
7376ad99 | 1324 | |
d4a0b124 | 1325 | if (head_target && !strcmp(head_target, ref->name)) { |
1a481123 VM |
1326 | if (git_reference_create_symbolic(&head, ref->owner, "HEAD", new_name, 1) < 0) { |
1327 | giterr_set(GITERR_REFERENCE, | |
45d387ac | 1328 | "Failed to update HEAD after renaming reference"); |
d4a0b124 | 1329 | goto cleanup; |
45d387ac | 1330 | } |
d4a0b124 VM |
1331 | } |
1332 | ||
a5cd086d MS |
1333 | /* |
1334 | * Rename the reflog file. | |
1335 | */ | |
1a481123 | 1336 | if (git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0) |
97769280 RB |
1337 | goto cleanup; |
1338 | ||
1a481123 VM |
1339 | if (git_path_exists(aux_path.ptr) == true) { |
1340 | if (git_reflog_rename(ref, new_name) < 0) | |
45d387ac | 1341 | goto cleanup; |
1a481123 VM |
1342 | } else { |
1343 | giterr_clear(); | |
45d387ac | 1344 | } |
a5cd086d | 1345 | |
d4a0b124 VM |
1346 | /* |
1347 | * Change the name of the reference given by the user. | |
1348 | */ | |
1349 | git__free(ref->name); | |
1350 | ref->name = git__strdup(new_name); | |
1351 | ||
1352 | /* The reference is no longer packed */ | |
1353 | ref->flags &= ~GIT_REF_PACKED; | |
7376ad99 | 1354 | |
45d387ac VM |
1355 | git_reference_free(head); |
1356 | git_buf_free(&aux_path); | |
1357 | return 0; | |
1358 | ||
0ffcf78a | 1359 | cleanup: |
d4a0b124 | 1360 | git_reference_free(head); |
97769280 | 1361 | git_buf_free(&aux_path); |
45d387ac | 1362 | return -1; |
7376ad99 | 1363 | |
0ffcf78a MS |
1364 | rollback: |
1365 | /* | |
45d387ac | 1366 | * Try to create the old reference again, ignore failures |
0ffcf78a | 1367 | */ |
d4a0b124 | 1368 | if (ref->flags & GIT_REF_SYMBOLIC) |
45d387ac | 1369 | git_reference_create_symbolic( |
1a481123 | 1370 | NULL, ref->owner, ref->name, ref->target.symbolic, 0); |
0ffcf78a | 1371 | else |
45d387ac | 1372 | git_reference_create_oid( |
1a481123 | 1373 | NULL, ref->owner, ref->name, &ref->target.oid, 0); |
0ffcf78a | 1374 | |
64093ce5 MS |
1375 | /* The reference is no longer packed */ |
1376 | ref->flags &= ~GIT_REF_PACKED; | |
1377 | ||
97769280 | 1378 | git_buf_free(&aux_path); |
45d387ac | 1379 | return -1; |
0ffcf78a | 1380 | } |
7376ad99 | 1381 | |
1a481123 | 1382 | int git_reference_resolve(git_reference **ref_out, git_reference *ref) |
87d3acf4 | 1383 | { |
45d387ac | 1384 | int result, i = 0; |
d4a0b124 | 1385 | git_repository *repo; |
87d3acf4 VM |
1386 | |
1387 | assert(ref); | |
1388 | ||
d4a0b124 VM |
1389 | *ref_out = NULL; |
1390 | repo = ref->owner; | |
87d3acf4 | 1391 | |
d4a0b124 VM |
1392 | /* If the reference is already resolved, we need to return a |
1393 | * copy. Instead of duplicating `ref`, we look it up again to | |
1394 | * ensure the copy is out to date */ | |
1395 | if (ref->flags & GIT_REF_OID) | |
1a481123 | 1396 | return git_reference_lookup(ref_out, ref->owner, ref->name); |
7341bf87 | 1397 | |
d4a0b124 VM |
1398 | /* Otherwise, keep iterating until the reference is resolved */ |
1399 | for (i = 0; i < MAX_NESTING_LEVEL; ++i) { | |
1400 | git_reference *new_ref; | |
932d1baf | 1401 | |
1a481123 | 1402 | result = git_reference_lookup(&new_ref, repo, ref->target.symbolic); |
45d387ac VM |
1403 | if (result < 0) |
1404 | return result; | |
87d3acf4 | 1405 | |
d4a0b124 VM |
1406 | /* Free intermediate references, except for the original one |
1407 | * we've received */ | |
1408 | if (i > 0) | |
1409 | git_reference_free(ref); | |
87d3acf4 | 1410 | |
d4a0b124 | 1411 | ref = new_ref; |
68a146c1 | 1412 | |
d4a0b124 VM |
1413 | /* When the reference we've just looked up is an OID, we've |
1414 | * successfully resolved the symbolic ref */ | |
1415 | if (ref->flags & GIT_REF_OID) { | |
1416 | *ref_out = ref; | |
1a481123 | 1417 | return 0; |
d4a0b124 | 1418 | } |
87d3acf4 VM |
1419 | } |
1420 | ||
1a481123 | 1421 | giterr_set(GITERR_REFERENCE, |
45d387ac VM |
1422 | "Symbolic reference too nested (%d levels deep)", MAX_NESTING_LEVEL); |
1423 | ||
1424 | return -1; | |
a46ec457 MS |
1425 | } |
1426 | ||
1a481123 | 1427 | int git_reference_packall(git_repository *repo) |
87d3acf4 | 1428 | { |
1a481123 VM |
1429 | if (packed_load(repo) < 0 || /* load the existing packfile */ |
1430 | packed_loadloose(repo) < 0 || /* add all the loose refs */ | |
1431 | packed_write(repo) < 0) /* write back to disk */ | |
45d387ac | 1432 | return -1; |
87d3acf4 | 1433 | |
45d387ac | 1434 | return 0; |
87d3acf4 VM |
1435 | } |
1436 | ||
d4a0b124 VM |
1437 | int git_reference_foreach( |
1438 | git_repository *repo, | |
1439 | unsigned int list_flags, | |
1440 | int (*callback)(const char *, void *), | |
1a481123 | 1441 | void *payload) |
00571828 | 1442 | { |
45d387ac | 1443 | int result; |
00571828 | 1444 | struct dirent_list_data data; |
97769280 | 1445 | git_buf refs_path = GIT_BUF_INIT; |
00571828 | 1446 | |
7ad96e51 | 1447 | /* list all the packed references first */ |
00571828 VM |
1448 | if (list_flags & GIT_REF_PACKED) { |
1449 | const char *ref_name; | |
00571828 | 1450 | |
1a481123 | 1451 | if (packed_load(repo) < 0) |
45d387ac | 1452 | return -1; |
00571828 | 1453 | |
1a481123 | 1454 | GIT_HASHTABLE_FOREACH_KEY(repo->references.packfile, ref_name, |
45d387ac VM |
1455 | if (callback(ref_name, payload) < 0) |
1456 | return 0; | |
00571828 VM |
1457 | ); |
1458 | } | |
1459 | ||
7ad96e51 VM |
1460 | /* now list the loose references, trying not to |
1461 | * duplicate the ref names already in the packed-refs file */ | |
09e8de0f VM |
1462 | |
1463 | data.repo_path_len = strlen(repo->path_repository); | |
1464 | data.list_flags = list_flags; | |
1465 | data.repo = repo; | |
1466 | data.callback = callback; | |
1467 | data.callback_payload = payload; | |
1468 | ||
1a481123 | 1469 | if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0) |
45d387ac | 1470 | return -1; |
97769280 | 1471 | |
1a481123 | 1472 | result = git_path_direach(&refs_path, _dirent_loose_listall, &data); |
97769280 RB |
1473 | git_buf_free(&refs_path); |
1474 | ||
45d387ac | 1475 | return result; |
09e8de0f VM |
1476 | } |
1477 | ||
d568d585 | 1478 | static int cb__reflist_add(const char *ref, void *data) |
09e8de0f VM |
1479 | { |
1480 | return git_vector_insert((git_vector *)data, git__strdup(ref)); | |
1481 | } | |
1482 | ||
d4a0b124 VM |
1483 | int git_reference_listall( |
1484 | git_strarray *array, | |
1485 | git_repository *repo, | |
1a481123 | 1486 | unsigned int list_flags) |
09e8de0f | 1487 | { |
09e8de0f VM |
1488 | git_vector ref_list; |
1489 | ||
1490 | assert(array && repo); | |
1491 | ||
1492 | array->strings = NULL; | |
1493 | array->count = 0; | |
1494 | ||
0d0fa7c3 | 1495 | if (git_vector_init(&ref_list, 8, NULL) < 0) |
45d387ac | 1496 | return -1; |
7ad96e51 | 1497 | |
45d387ac | 1498 | if (git_reference_foreach( |
1a481123 | 1499 | repo, list_flags, &cb__reflist_add, (void *)&ref_list) < 0) { |
09e8de0f | 1500 | git_vector_free(&ref_list); |
45d387ac | 1501 | return -1; |
7ad96e51 VM |
1502 | } |
1503 | ||
09e8de0f VM |
1504 | array->strings = (char **)ref_list.contents; |
1505 | array->count = ref_list.length; | |
1a481123 | 1506 | return 0; |
00571828 | 1507 | } |
87d3acf4 | 1508 | |
1a481123 | 1509 | int git_reference_reload(git_reference *ref) |
2f8a8ab2 | 1510 | { |
1a481123 | 1511 | return reference_lookup(ref); |
2f8a8ab2 | 1512 | } |
9282e921 | 1513 | |
2f8a8ab2 VM |
1514 | void git_repository__refcache_free(git_refcache *refs) |
1515 | { | |
2f8a8ab2 VM |
1516 | assert(refs); |
1517 | ||
87d3acf4 | 1518 | if (refs->packfile) { |
d4a0b124 VM |
1519 | struct packref *reference; |
1520 | ||
854eccbb RB |
1521 | GIT_HASHTABLE_FOREACH_VALUE( |
1522 | refs->packfile, reference, git__free(reference)); | |
87d3acf4 VM |
1523 | |
1524 | git_hashtable_free(refs->packfile); | |
1525 | } | |
9282e921 | 1526 | } |
2f8a8ab2 | 1527 | |
d4a0b124 | 1528 | static int is_valid_ref_char(char ch) |
aa2120e9 | 1529 | { |
50a8fd03 | 1530 | if ((unsigned) ch <= ' ') |
d4a0b124 | 1531 | return 0; |
aa2120e9 | 1532 | |
1533 | switch (ch) { | |
1534 | case '~': | |
1535 | case '^': | |
1536 | case ':': | |
1537 | case '\\': | |
1538 | case '?': | |
1539 | case '[': | |
e1be1028 | 1540 | case '*': |
d4a0b124 | 1541 | return 0; |
aa2120e9 | 1542 | default: |
d4a0b124 | 1543 | return 1; |
aa2120e9 | 1544 | } |
1545 | } | |
1546 | ||
d4a0b124 VM |
1547 | static int normalize_name( |
1548 | char *buffer_out, | |
1549 | size_t out_size, | |
1550 | const char *name, | |
1551 | int is_oid_ref) | |
aa2120e9 | 1552 | { |
aa2120e9 | 1553 | const char *name_end, *buffer_out_start; |
b75bec94 | 1554 | const char *current; |
aa2120e9 | 1555 | int contains_a_slash = 0; |
1556 | ||
1557 | assert(name && buffer_out); | |
1558 | ||
1559 | buffer_out_start = buffer_out; | |
b75bec94 | 1560 | current = name; |
aa2120e9 | 1561 | name_end = name + strlen(name); |
1562 | ||
3101a3e5 VM |
1563 | /* Terminating null byte */ |
1564 | out_size--; | |
1565 | ||
aa2120e9 | 1566 | /* A refname can not be empty */ |
1567 | if (name_end == name) | |
1a481123 | 1568 | goto invalid_name; |
aa2120e9 | 1569 | |
1570 | /* A refname can not end with a dot or a slash */ | |
1571 | if (*(name_end - 1) == '.' || *(name_end - 1) == '/') | |
1a481123 | 1572 | goto invalid_name; |
aa2120e9 | 1573 | |
3101a3e5 | 1574 | while (current < name_end && out_size) { |
d4a0b124 | 1575 | if (!is_valid_ref_char(*current)) |
1a481123 | 1576 | goto invalid_name; |
aa2120e9 | 1577 | |
1578 | if (buffer_out > buffer_out_start) { | |
1579 | char prev = *(buffer_out - 1); | |
1580 | ||
1581 | /* A refname can not start with a dot nor contain a double dot */ | |
1582 | if (*current == '.' && ((prev == '.') || (prev == '/'))) | |
1a481123 | 1583 | goto invalid_name; |
aa2120e9 | 1584 | |
1585 | /* '@{' is forbidden within a refname */ | |
1586 | if (*current == '{' && prev == '@') | |
1a481123 | 1587 | goto invalid_name; |
aa2120e9 | 1588 | |
1589 | /* Prevent multiple slashes from being added to the output */ | |
1590 | if (*current == '/' && prev == '/') { | |
1591 | current++; | |
1592 | continue; | |
1593 | } | |
1594 | } | |
1595 | ||
e1be1028 | 1596 | if (*current == '/') |
aa2120e9 | 1597 | contains_a_slash = 1; |
aa2120e9 | 1598 | |
1599 | *buffer_out++ = *current++; | |
3101a3e5 | 1600 | out_size--; |
aa2120e9 | 1601 | } |
1602 | ||
3101a3e5 | 1603 | if (!out_size) |
1a481123 | 1604 | goto invalid_name; |
3101a3e5 | 1605 | |
83c95128 | 1606 | /* Object id refname have to contain at least one slash, except |
df30eac1 JP |
1607 | * for HEAD in a detached state or MERGE_HEAD if we're in the |
1608 | * middle of a merge */ | |
d4a0b124 VM |
1609 | if (is_oid_ref && |
1610 | !contains_a_slash && | |
1611 | strcmp(name, GIT_HEAD_FILE) != 0 && | |
1612 | strcmp(name, GIT_MERGE_HEAD_FILE) != 0 && | |
1613 | strcmp(name, GIT_FETCH_HEAD_FILE) != 0) | |
1a481123 | 1614 | goto invalid_name; |
aa2120e9 | 1615 | |
1616 | /* A refname can not end with ".lock" */ | |
1617 | if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION)) | |
1a481123 | 1618 | goto invalid_name; |
aa2120e9 | 1619 | |
1620 | *buffer_out = '\0'; | |
1621 | ||
83c95128 CMN |
1622 | /* |
1623 | * For object id references, name has to start with refs/. Again, | |
1624 | * we need to allow HEAD to be in a detached state. | |
1625 | */ | |
0d5d5190 VM |
1626 | if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) || |
1627 | strcmp(buffer_out_start, GIT_HEAD_FILE))) | |
1a481123 | 1628 | goto invalid_name; |
aa2120e9 | 1629 | |
1a481123 VM |
1630 | return 0; |
1631 | ||
1632 | invalid_name: | |
1633 | giterr_set(GITERR_REFERENCE, "The given reference name is not valid"); | |
1634 | return -1; | |
aa2120e9 | 1635 | } |
2f8a8ab2 | 1636 | |
d4a0b124 VM |
1637 | int git_reference__normalize_name( |
1638 | char *buffer_out, | |
1639 | size_t out_size, | |
1640 | const char *name) | |
86194b24 | 1641 | { |
3101a3e5 | 1642 | return normalize_name(buffer_out, out_size, name, 0); |
86194b24 VM |
1643 | } |
1644 | ||
d4a0b124 VM |
1645 | int git_reference__normalize_name_oid( |
1646 | char *buffer_out, | |
1647 | size_t out_size, | |
1648 | const char *name) | |
86194b24 | 1649 | { |
3101a3e5 | 1650 | return normalize_name(buffer_out, out_size, name, 1); |
50a8fd03 | 1651 | } |