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