]>
Commit | Line | Data |
---|---|---|
9282e921 | 1 | /* |
2 | * This file is free software; you can redistribute it and/or modify | |
3 | * it under the terms of the GNU General Public License, version 2, | |
4 | * as published by the Free Software Foundation. | |
5 | * | |
6 | * In addition to the permissions in the GNU General Public License, | |
7 | * the authors give you unlimited permission to link the compiled | |
8 | * version of this file into combinations with other programs, | |
9 | * and to distribute those combinations without any restriction | |
10 | * coming from the use of this file. (The General Public License | |
11 | * restrictions do apply in other respects; for example, they cover | |
12 | * modification of the file, and distribution when not linked into | |
13 | * a combined executable.) | |
14 | * | |
15 | * This file is distributed in the hope that it will be useful, but | |
16 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
18 | * General Public License for more details. | |
19 | * | |
20 | * You should have received a copy of the GNU General Public License | |
21 | * along with this program; see the file COPYING. If not, write to | |
22 | * the Free Software Foundation, 51 Franklin Street, Fifth Floor, | |
23 | * Boston, MA 02110-1301, USA. | |
24 | */ | |
25 | ||
26 | #include "refs.h" | |
27 | #include "hash.h" | |
28 | #include "repository.h" | |
29 | #include "fileops.h" | |
30 | ||
87d3acf4 VM |
31 | #include <git2/tag.h> |
32 | #include <git2/object.h> | |
33 | ||
9282e921 | 34 | #define MAX_NESTING_LEVEL 5 |
35 | ||
86194b24 VM |
36 | typedef struct { |
37 | git_reference ref; | |
38 | git_oid oid; | |
39 | git_oid peel_target; | |
40 | } reference_oid; | |
41 | ||
42 | typedef struct { | |
43 | git_reference ref; | |
44 | char *target; | |
45 | } reference_symbolic; | |
46 | ||
9282e921 | 47 | static const int default_table_size = 32; |
48 | ||
fc658755 | 49 | static uint32_t reftable_hash(const void *key, int hash_id) |
9282e921 | 50 | { |
fc658755 VM |
51 | static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = { |
52 | 2147483647, | |
53 | 0x5d20bb23, | |
54 | 0x7daaab3c | |
55 | }; | |
9282e921 | 56 | |
fc658755 | 57 | return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]); |
9282e921 | 58 | } |
59 | ||
87d3acf4 VM |
60 | static void reference_free(git_reference *reference); |
61 | static int reference_create(git_reference **ref_out, git_repository *repo, const char *name, git_rtype type); | |
7341bf87 | 62 | static int reference_read(gitfo_buf *file_content, time_t *mtime, const char *repo_path, const char *ref_name); |
87d3acf4 VM |
63 | |
64 | /* loose refs */ | |
65 | static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content); | |
66 | static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content); | |
7341bf87 | 67 | static int loose_lookup(git_reference **ref_out, git_repository *repo, const char *name, int skip_symbolic); |
87d3acf4 | 68 | static int loose_write(git_reference *ref); |
7341bf87 | 69 | static int loose_update(git_reference *ref); |
87d3acf4 VM |
70 | |
71 | /* packed refs */ | |
87d3acf4 VM |
72 | static int packed_parse_peel(reference_oid *tag_ref, const char **buffer_out, const char *buffer_end); |
73 | static int packed_parse_oid(reference_oid **ref_out, git_repository *repo, const char **buffer_out, const char *buffer_end); | |
74 | static int packed_load(git_repository *repo); | |
75 | static int packed_loadloose(git_repository *repository); | |
76 | static int packed_write_ref(reference_oid *ref, git_filebuf *file); | |
77 | static int packed_find_peel(reference_oid *ref); | |
78 | static int packed_remove_loose(git_repository *repo, git_vector *packing_list); | |
79 | static int packed_sort(const void *a, const void *b); | |
80 | static int packed_write(git_repository *repo); | |
81 | ||
95cde17c | 82 | /* internal helpers */ |
1b6d8163 | 83 | static int reference_available(git_repository *repo, const char *ref, const char *old_ref); |
95cde17c | 84 | |
87d3acf4 VM |
85 | /* name normalization */ |
86 | static int check_valid_ref_char(char ch); | |
3101a3e5 | 87 | static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref); |
87d3acf4 VM |
88 | |
89 | /***************************************** | |
90 | * Internal methods - Constructor/destructor | |
91 | *****************************************/ | |
2f8a8ab2 | 92 | static void reference_free(git_reference *reference) |
9282e921 | 93 | { |
2f8a8ab2 VM |
94 | if (reference == NULL) |
95 | return; | |
9282e921 | 96 | |
2f8a8ab2 VM |
97 | if (reference->name) |
98 | free(reference->name); | |
9282e921 | 99 | |
2f8a8ab2 | 100 | if (reference->type == GIT_REF_SYMBOLIC) |
86194b24 | 101 | free(((reference_symbolic *)reference)->target); |
9282e921 | 102 | |
2f8a8ab2 | 103 | free(reference); |
9282e921 | 104 | } |
105 | ||
87d3acf4 VM |
106 | static int reference_create( |
107 | git_reference **ref_out, | |
108 | git_repository *repo, | |
109 | const char *name, | |
110 | git_rtype type) | |
111 | { | |
3101a3e5 | 112 | char normalized[GIT_REFNAME_MAX]; |
86194b24 | 113 | int error = GIT_SUCCESS, size; |
9282e921 | 114 | git_reference *reference = NULL; |
115 | ||
1d8cc731 | 116 | assert(ref_out && repo && name); |
117 | ||
86194b24 VM |
118 | if (type == GIT_REF_SYMBOLIC) |
119 | size = sizeof(reference_symbolic); | |
120 | else if (type == GIT_REF_OID) | |
121 | size = sizeof(reference_oid); | |
122 | else | |
fa59f18d VM |
123 | return git__throw(GIT_EINVALIDARGS, |
124 | "Invalid reference type. Use either GIT_REF_OID or GIT_REF_SYMBOLIC as type specifier"); | |
9282e921 | 125 | |
86194b24 | 126 | reference = git__malloc(size); |
9282e921 | 127 | if (reference == NULL) |
128 | return GIT_ENOMEM; | |
129 | ||
86194b24 | 130 | memset(reference, 0x0, size); |
2f8a8ab2 | 131 | reference->owner = repo; |
1d8cc731 | 132 | reference->type = type; |
133 | ||
3101a3e5 | 134 | error = normalize_name(normalized, sizeof(normalized), name, (type & GIT_REF_OID)); |
1d8cc731 | 135 | if (error < GIT_SUCCESS) |
136 | goto cleanup; | |
137 | ||
138 | reference->name = git__strdup(normalized); | |
139 | if (reference->name == NULL) { | |
140 | error = GIT_ENOMEM; | |
141 | goto cleanup; | |
142 | } | |
9282e921 | 143 | |
2f8a8ab2 | 144 | *ref_out = reference; |
1d8cc731 | 145 | |
5bdf7b9f | 146 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference"); |
1d8cc731 | 147 | |
148 | cleanup: | |
149 | reference_free(reference); | |
5bdf7b9f | 150 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference"); |
1d8cc731 | 151 | } |
152 | ||
7341bf87 VM |
153 | static int reference_read(gitfo_buf *file_content, time_t *mtime, const char *repo_path, const char *ref_name) |
154 | { | |
155 | struct stat st; | |
156 | char path[GIT_PATH_MAX]; | |
157 | ||
158 | /* Determine the full path of the file */ | |
159 | git__joinpath(path, repo_path, ref_name); | |
160 | ||
fa59f18d VM |
161 | if (gitfo_stat(path, &st) < 0 || S_ISDIR(st.st_mode)) |
162 | return git__throw(GIT_ENOTFOUND, | |
163 | "Cannot read reference file '%s'", ref_name); | |
7341bf87 VM |
164 | |
165 | if (mtime) | |
166 | *mtime = st.st_mtime; | |
167 | ||
168 | if (file_content) | |
169 | return gitfo_read_file(file_content, path); | |
170 | ||
171 | return GIT_SUCCESS; | |
172 | } | |
173 | ||
1d8cc731 | 174 | |
1d8cc731 | 175 | |
9282e921 | 176 | |
87d3acf4 VM |
177 | /***************************************** |
178 | * Internal methods - Loose references | |
179 | *****************************************/ | |
7341bf87 VM |
180 | static int loose_update(git_reference *ref) |
181 | { | |
182 | int error; | |
183 | time_t ref_time; | |
184 | gitfo_buf ref_file = GITFO_BUF_INIT; | |
185 | ||
186 | if (ref->type & GIT_REF_PACKED) | |
187 | return packed_load(ref->owner); | |
188 | ||
189 | error = reference_read(NULL, &ref_time, ref->owner->path_repository, ref->name); | |
190 | if (error < GIT_SUCCESS) | |
191 | goto cleanup; | |
192 | ||
193 | if (ref_time == ref->mtime) | |
194 | return GIT_SUCCESS; | |
195 | ||
196 | error = reference_read(&ref_file, &ref->mtime, ref->owner->path_repository, ref->name); | |
197 | if (error < GIT_SUCCESS) | |
198 | goto cleanup; | |
199 | ||
200 | if (ref->type == GIT_REF_SYMBOLIC) | |
201 | error = loose_parse_symbolic(ref, &ref_file); | |
202 | else if (ref->type == GIT_REF_OID) | |
203 | error = loose_parse_oid(ref, &ref_file); | |
204 | else | |
fa59f18d VM |
205 | error = git__throw(GIT_EOBJCORRUPTED, |
206 | "Invalid reference type (%d) for loose reference", ref->type); | |
7341bf87 VM |
207 | |
208 | gitfo_free_buf(&ref_file); | |
209 | ||
210 | cleanup: | |
211 | if (error != GIT_SUCCESS) { | |
212 | reference_free(ref); | |
213 | git_hashtable_remove(ref->owner->references.loose_cache, ref->name); | |
214 | } | |
215 | ||
5bdf7b9f | 216 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to update loose reference"); |
7341bf87 VM |
217 | } |
218 | ||
87d3acf4 | 219 | static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content) |
2f8a8ab2 | 220 | { |
ddc9e79a | 221 | const unsigned int header_len = strlen(GIT_SYMREF); |
2f8a8ab2 VM |
222 | const char *refname_start; |
223 | char *eol; | |
86194b24 | 224 | reference_symbolic *ref_sym; |
9282e921 | 225 | |
2f8a8ab2 | 226 | refname_start = (const char *)file_content->data; |
86194b24 | 227 | ref_sym = (reference_symbolic *)ref; |
9282e921 | 228 | |
2f8a8ab2 | 229 | if (file_content->len < (header_len + 1)) |
fa59f18d VM |
230 | return git__throw(GIT_EOBJCORRUPTED, |
231 | "Failed to parse loose reference. Object too short"); | |
9282e921 | 232 | |
2f8a8ab2 VM |
233 | /* |
234 | * Assume we have already checked for the header | |
235 | * before calling this function | |
236 | */ | |
9282e921 | 237 | |
2f8a8ab2 | 238 | refname_start += header_len; |
9282e921 | 239 | |
7341bf87 | 240 | free(ref_sym->target); |
86194b24 VM |
241 | ref_sym->target = git__strdup(refname_start); |
242 | if (ref_sym->target == NULL) | |
32054c24 | 243 | return GIT_ENOMEM; |
9282e921 | 244 | |
2f8a8ab2 | 245 | /* remove newline at the end of file */ |
86194b24 | 246 | eol = strchr(ref_sym->target, '\n'); |
ff5873ad | 247 | if (eol == NULL) |
fa59f18d VM |
248 | return git__throw(GIT_EOBJCORRUPTED, |
249 | "Failed to parse loose reference. Missing EOL"); | |
ff5873ad VM |
250 | |
251 | *eol = '\0'; | |
252 | if (eol[-1] == '\r') | |
253 | eol[-1] = '\0'; | |
9282e921 | 254 | |
2f8a8ab2 | 255 | return GIT_SUCCESS; |
9282e921 | 256 | } |
257 | ||
87d3acf4 | 258 | static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content) |
2f8a8ab2 | 259 | { |
fa59f18d | 260 | int error; |
86194b24 | 261 | reference_oid *ref_oid; |
2f8a8ab2 | 262 | char *buffer; |
86194b24 | 263 | |
2f8a8ab2 | 264 | buffer = (char *)file_content->data; |
86194b24 | 265 | ref_oid = (reference_oid *)ref; |
9282e921 | 266 | |
2f8a8ab2 | 267 | /* File format: 40 chars (OID) + newline */ |
ff5873ad | 268 | if (file_content->len < GIT_OID_HEXSZ + 1) |
fa59f18d VM |
269 | return git__throw(GIT_EOBJCORRUPTED, |
270 | "Failed to parse loose reference. Reference too short"); | |
9282e921 | 271 | |
fa48608e | 272 | if ((error = git_oid_fromstr(&ref_oid->oid, buffer)) < GIT_SUCCESS) |
fa59f18d | 273 | return git__rethrow(GIT_EOBJCORRUPTED, "Failed to parse loose reference."); |
9282e921 | 274 | |
ff5873ad VM |
275 | buffer = buffer + GIT_OID_HEXSZ; |
276 | if (*buffer == '\r') | |
277 | buffer++; | |
278 | ||
279 | if (*buffer != '\n') | |
fa59f18d VM |
280 | return git__throw(GIT_EOBJCORRUPTED, |
281 | "Failed to parse loose reference. Missing EOL"); | |
9282e921 | 282 | |
2f8a8ab2 | 283 | return GIT_SUCCESS; |
9282e921 | 284 | } |
285 | ||
9282e921 | 286 | |
00571828 VM |
287 | static git_rtype loose_guess_rtype(const char *full_path) |
288 | { | |
289 | gitfo_buf ref_file = GITFO_BUF_INIT; | |
290 | git_rtype type; | |
291 | ||
292 | type = GIT_REF_INVALID; | |
293 | ||
294 | if (gitfo_read_file(&ref_file, full_path) == GIT_SUCCESS) { | |
295 | if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) | |
296 | type = GIT_REF_SYMBOLIC; | |
297 | else | |
298 | type = GIT_REF_OID; | |
299 | } | |
300 | ||
301 | gitfo_free_buf(&ref_file); | |
302 | return type; | |
303 | } | |
304 | ||
87d3acf4 | 305 | static int loose_lookup( |
2f8a8ab2 VM |
306 | git_reference **ref_out, |
307 | git_repository *repo, | |
87d3acf4 VM |
308 | const char *name, |
309 | int skip_symbolic) | |
9282e921 | 310 | { |
311 | int error = GIT_SUCCESS; | |
2f8a8ab2 VM |
312 | gitfo_buf ref_file = GITFO_BUF_INIT; |
313 | git_reference *ref = NULL; | |
7341bf87 | 314 | time_t ref_time; |
9282e921 | 315 | |
2f8a8ab2 | 316 | *ref_out = NULL; |
9282e921 | 317 | |
7341bf87 | 318 | error = reference_read(&ref_file, &ref_time, repo->path_repository, name); |
9282e921 | 319 | if (error < GIT_SUCCESS) |
320 | goto cleanup; | |
321 | ||
1d8cc731 | 322 | if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) { |
87d3acf4 VM |
323 | if (skip_symbolic) |
324 | return GIT_SUCCESS; | |
325 | ||
32054c24 | 326 | error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC); |
1d8cc731 | 327 | if (error < GIT_SUCCESS) |
328 | goto cleanup; | |
9282e921 | 329 | |
87d3acf4 | 330 | error = loose_parse_symbolic(ref, &ref_file); |
1d8cc731 | 331 | } else { |
32054c24 | 332 | error = reference_create(&ref, repo, name, GIT_REF_OID); |
1d8cc731 | 333 | if (error < GIT_SUCCESS) |
334 | goto cleanup; | |
335 | ||
87d3acf4 | 336 | error = loose_parse_oid(ref, &ref_file); |
1d8cc731 | 337 | } |
338 | ||
ff5873ad VM |
339 | if (error < GIT_SUCCESS) |
340 | goto cleanup; | |
341 | ||
7341bf87 | 342 | ref->mtime = ref_time; |
2f8a8ab2 | 343 | *ref_out = ref; |
567fc1d2 | 344 | gitfo_free_buf(&ref_file); |
2f8a8ab2 | 345 | return GIT_SUCCESS; |
9282e921 | 346 | |
347 | cleanup: | |
2f8a8ab2 VM |
348 | gitfo_free_buf(&ref_file); |
349 | reference_free(ref); | |
5bdf7b9f | 350 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to lookup loose reference"); |
9282e921 | 351 | } |
352 | ||
87d3acf4 VM |
353 | static int loose_write(git_reference *ref) |
354 | { | |
355 | git_filebuf file; | |
356 | char ref_path[GIT_PATH_MAX]; | |
e7e0e20f | 357 | int error; |
7341bf87 | 358 | struct stat st; |
87d3acf4 | 359 | |
76f7cf70 | 360 | assert((ref->type & GIT_REF_PACKED) == 0); |
361 | ||
87d3acf4 VM |
362 | git__joinpath(ref_path, ref->owner->path_repository, ref->name); |
363 | ||
55ffebe3 | 364 | if ((error = git_filebuf_open(&file, ref_path, GIT_FILEBUF_FORCE)) < GIT_SUCCESS) |
5bdf7b9f | 365 | return git__rethrow(error, "Failed to write loose reference"); |
87d3acf4 VM |
366 | |
367 | if (ref->type & GIT_REF_OID) { | |
368 | reference_oid *ref_oid = (reference_oid *)ref; | |
e7e0e20f | 369 | char oid[GIT_OID_HEXSZ + 1]; |
87d3acf4 | 370 | |
e7e0e20f | 371 | memset(oid, 0x0, sizeof(oid)); |
87d3acf4 | 372 | |
e7e0e20f CMN |
373 | git_oid_fmt(oid, &ref_oid->oid); |
374 | error = git_filebuf_printf(&file, "%s\n", oid); | |
375 | if (error < GIT_SUCCESS) | |
376 | goto unlock; | |
87d3acf4 VM |
377 | |
378 | } else if (ref->type & GIT_REF_SYMBOLIC) { /* GIT_REF_SYMBOLIC */ | |
379 | reference_symbolic *ref_sym = (reference_symbolic *)ref; | |
380 | ||
e7e0e20f | 381 | error = git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref_sym->target); |
87d3acf4 | 382 | } else { |
fa59f18d | 383 | error = git__throw(GIT_EOBJCORRUPTED, "Failed to write reference. Invalid reference type"); |
87d3acf4 VM |
384 | goto unlock; |
385 | } | |
386 | ||
87d3acf4 VM |
387 | error = git_filebuf_commit(&file); |
388 | ||
7341bf87 VM |
389 | if (gitfo_stat(ref_path, &st) == GIT_SUCCESS) |
390 | ref->mtime = st.st_mtime; | |
391 | ||
5bdf7b9f | 392 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference"); |
87d3acf4 VM |
393 | |
394 | unlock: | |
395 | git_filebuf_cleanup(&file); | |
5bdf7b9f | 396 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference"); |
87d3acf4 VM |
397 | } |
398 | ||
399 | ||
400 | ||
401 | ||
402 | ||
2f8a8ab2 | 403 | |
87d3acf4 VM |
404 | /***************************************** |
405 | * Internal methods - Packed references | |
406 | *****************************************/ | |
9282e921 | 407 | |
87d3acf4 | 408 | static int packed_parse_peel( |
86194b24 | 409 | reference_oid *tag_ref, |
2f8a8ab2 VM |
410 | const char **buffer_out, |
411 | const char *buffer_end) | |
9282e921 | 412 | { |
2f8a8ab2 VM |
413 | const char *buffer = *buffer_out + 1; |
414 | ||
415 | assert(buffer[-1] == '^'); | |
9282e921 | 416 | |
417 | /* Ensure it's not the first entry of the file */ | |
2f8a8ab2 | 418 | if (tag_ref == NULL) |
5bdf7b9f | 419 | return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is the first entry of the file"); |
9282e921 | 420 | |
421 | /* Ensure reference is a tag */ | |
86194b24 | 422 | if (git__prefixcmp(tag_ref->ref.name, GIT_REFS_TAGS_DIR) != 0) |
5bdf7b9f | 423 | return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is not a tag"); |
9282e921 | 424 | |
2f8a8ab2 | 425 | if (buffer + GIT_OID_HEXSZ >= buffer_end) |
5bdf7b9f | 426 | return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer too small"); |
9282e921 | 427 | |
2f8a8ab2 | 428 | /* Is this a valid object id? */ |
fa48608e | 429 | if (git_oid_fromstr(&tag_ref->peel_target, buffer) < GIT_SUCCESS) |
5bdf7b9f | 430 | return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Not a valid object ID"); |
2f8a8ab2 | 431 | |
ff5873ad VM |
432 | buffer = buffer + GIT_OID_HEXSZ; |
433 | if (*buffer == '\r') | |
434 | buffer++; | |
435 | ||
436 | if (*buffer != '\n') | |
5bdf7b9f | 437 | return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer not terminated correctly"); |
ff5873ad VM |
438 | |
439 | *buffer_out = buffer + 1; | |
86194b24 | 440 | tag_ref->ref.type |= GIT_REF_HAS_PEEL; |
2f8a8ab2 | 441 | |
2f8a8ab2 | 442 | return GIT_SUCCESS; |
9282e921 | 443 | } |
444 | ||
87d3acf4 | 445 | static int packed_parse_oid( |
86194b24 | 446 | reference_oid **ref_out, |
2f8a8ab2 VM |
447 | git_repository *repo, |
448 | const char **buffer_out, | |
449 | const char *buffer_end) | |
9282e921 | 450 | { |
e06dd9b6 | 451 | reference_oid *ref = NULL; |
2f8a8ab2 VM |
452 | |
453 | const char *buffer = *buffer_out; | |
454 | const char *refname_begin, *refname_end; | |
455 | ||
9282e921 | 456 | int error = GIT_SUCCESS; |
9282e921 | 457 | int refname_len; |
3101a3e5 | 458 | char refname[GIT_REFNAME_MAX]; |
1d8cc731 | 459 | git_oid id; |
9282e921 | 460 | |
2f8a8ab2 VM |
461 | refname_begin = (buffer + GIT_OID_HEXSZ + 1); |
462 | if (refname_begin >= buffer_end || | |
463 | refname_begin[-1] != ' ') { | |
464 | error = GIT_EPACKEDREFSCORRUPTED; | |
465 | goto cleanup; | |
466 | } | |
9282e921 | 467 | |
2f8a8ab2 | 468 | /* Is this a valid object id? */ |
fa48608e | 469 | if ((error = git_oid_fromstr(&id, buffer)) < GIT_SUCCESS) |
2f8a8ab2 | 470 | goto cleanup; |
9282e921 | 471 | |
2f8a8ab2 VM |
472 | refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); |
473 | if (refname_end == NULL) { | |
474 | error = GIT_EPACKEDREFSCORRUPTED; | |
475 | goto cleanup; | |
476 | } | |
9282e921 | 477 | |
2f8a8ab2 | 478 | refname_len = refname_end - refname_begin; |
9282e921 | 479 | |
1d8cc731 | 480 | memcpy(refname, refname_begin, refname_len); |
481 | refname[refname_len] = 0; | |
9282e921 | 482 | |
1d8cc731 | 483 | if (refname[refname_len - 1] == '\r') |
484 | refname[refname_len - 1] = 0; | |
9282e921 | 485 | |
86194b24 | 486 | error = reference_create((git_reference **)&ref, repo, refname, GIT_REF_OID); |
1d8cc731 | 487 | if (error < GIT_SUCCESS) |
488 | goto cleanup; | |
489 | ||
86194b24 VM |
490 | git_oid_cpy(&ref->oid, &id); |
491 | ref->ref.type |= GIT_REF_PACKED; | |
9282e921 | 492 | |
2f8a8ab2 | 493 | *ref_out = ref; |
9282e921 | 494 | *buffer_out = refname_end + 1; |
495 | ||
2f8a8ab2 VM |
496 | return GIT_SUCCESS; |
497 | ||
498 | cleanup: | |
86194b24 | 499 | reference_free((git_reference *)ref); |
5bdf7b9f | 500 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse OID of packed reference"); |
9282e921 | 501 | } |
502 | ||
87d3acf4 | 503 | static int packed_load(git_repository *repo) |
9282e921 | 504 | { |
505 | int error = GIT_SUCCESS; | |
2f8a8ab2 VM |
506 | gitfo_buf packfile = GITFO_BUF_INIT; |
507 | const char *buffer_start, *buffer_end; | |
87d3acf4 VM |
508 | git_refcache *ref_cache = &repo->references; |
509 | ||
510 | /* already loaded */ | |
7341bf87 VM |
511 | if (repo->references.packfile != NULL) { |
512 | time_t packed_time; | |
87d3acf4 | 513 | |
7341bf87 VM |
514 | /* check if we can read the time of the index; |
515 | * if we can read it and it matches the time of the | |
516 | * index we had previously loaded, we don't need to do | |
517 | * anything else. | |
518 | * | |
519 | * if we cannot load the time (e.g. the packfile | |
520 | * has disappeared) or the time is different, we | |
521 | * have to reload the packfile */ | |
87d3acf4 | 522 | |
7341bf87 VM |
523 | if (!reference_read(NULL, &packed_time, repo->path_repository, GIT_PACKEDREFS_FILE) && |
524 | packed_time == ref_cache->packfile_time) | |
525 | return GIT_SUCCESS; | |
87d3acf4 | 526 | |
7341bf87 VM |
527 | git_hashtable_clear(repo->references.packfile); |
528 | } else { | |
529 | ref_cache->packfile = git_hashtable_alloc( | |
530 | default_table_size, | |
531 | reftable_hash, | |
532 | (git_hash_keyeq_ptr)strcmp); | |
533 | ||
534 | if (ref_cache->packfile == NULL) | |
535 | return GIT_ENOMEM; | |
536 | } | |
537 | ||
538 | /* read the packfile from disk; | |
539 | * store its modification time to check for future reloads */ | |
540 | error = reference_read( | |
541 | &packfile, | |
542 | &ref_cache->packfile_time, | |
543 | repo->path_repository, | |
544 | GIT_PACKEDREFS_FILE); | |
87d3acf4 VM |
545 | |
546 | /* there is no packfile on disk; that's ok */ | |
547 | if (error == GIT_ENOTFOUND) | |
548 | return GIT_SUCCESS; | |
2f8a8ab2 | 549 | |
9282e921 | 550 | if (error < GIT_SUCCESS) |
551 | goto cleanup; | |
552 | ||
2f8a8ab2 VM |
553 | buffer_start = (const char *)packfile.data; |
554 | buffer_end = (const char *)(buffer_start) + packfile.len; | |
9282e921 | 555 | |
7c8a7b91 VM |
556 | while (buffer_start < buffer_end && buffer_start[0] == '#') { |
557 | buffer_start = strchr(buffer_start, '\n'); | |
558 | if (buffer_start == NULL) { | |
559 | error = GIT_EPACKEDREFSCORRUPTED; | |
560 | goto cleanup; | |
561 | } | |
ddc9e79a | 562 | buffer_start++; |
7c8a7b91 | 563 | } |
ddc9e79a | 564 | |
9282e921 | 565 | while (buffer_start < buffer_end) { |
86194b24 | 566 | reference_oid *ref = NULL; |
2f8a8ab2 | 567 | |
87d3acf4 | 568 | error = packed_parse_oid(&ref, repo, &buffer_start, buffer_end); |
2f8a8ab2 VM |
569 | if (error < GIT_SUCCESS) |
570 | goto cleanup; | |
571 | ||
9282e921 | 572 | if (buffer_start[0] == '^') { |
87d3acf4 | 573 | error = packed_parse_peel(ref, &buffer_start, buffer_end); |
9282e921 | 574 | if (error < GIT_SUCCESS) |
575 | goto cleanup; | |
2f8a8ab2 | 576 | } |
9282e921 | 577 | |
87d3acf4 | 578 | error = git_hashtable_insert(ref_cache->packfile, ref->ref.name, ref); |
2f8a8ab2 | 579 | if (error < GIT_SUCCESS) { |
86194b24 | 580 | reference_free((git_reference *)ref); |
9282e921 | 581 | goto cleanup; |
2f8a8ab2 VM |
582 | } |
583 | } | |
9282e921 | 584 | |
7341bf87 VM |
585 | gitfo_free_buf(&packfile); |
586 | return GIT_SUCCESS; | |
587 | ||
2f8a8ab2 | 588 | cleanup: |
7341bf87 VM |
589 | git_hashtable_free(ref_cache->packfile); |
590 | ref_cache->packfile = NULL; | |
2f8a8ab2 | 591 | gitfo_free_buf(&packfile); |
5bdf7b9f | 592 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load packed references"); |
2f8a8ab2 | 593 | } |
9282e921 | 594 | |
00571828 VM |
595 | |
596 | ||
597 | ||
598 | struct dirent_list_data { | |
7ad96e51 | 599 | git_repository *repo; |
00571828 VM |
600 | size_t repo_path_len; |
601 | unsigned int list_flags; | |
09e8de0f VM |
602 | |
603 | int (*callback)(const char *, void *); | |
604 | void *callback_payload; | |
00571828 VM |
605 | }; |
606 | ||
607 | static int _dirent_loose_listall(void *_data, char *full_path) | |
608 | { | |
609 | struct dirent_list_data *data = (struct dirent_list_data *)_data; | |
7ad96e51 | 610 | char *file_path = full_path + data->repo_path_len; |
00571828 VM |
611 | |
612 | if (gitfo_isdir(full_path) == GIT_SUCCESS) | |
613 | return gitfo_dirent(full_path, GIT_PATH_MAX, _dirent_loose_listall, _data); | |
614 | ||
7ad96e51 | 615 | /* do not add twice a reference that exists already in the packfile */ |
b5abb881 VM |
616 | if ((data->list_flags & GIT_REF_PACKED) != 0 && |
617 | git_hashtable_lookup(data->repo->references.packfile, file_path) != NULL) | |
7ad96e51 VM |
618 | return GIT_SUCCESS; |
619 | ||
09e8de0f VM |
620 | if (data->list_flags != GIT_REF_LISTALL) { |
621 | if ((data->list_flags & loose_guess_rtype(full_path)) == 0) | |
622 | return GIT_SUCCESS; /* we are filtering out this reference */ | |
623 | } | |
00571828 | 624 | |
09e8de0f | 625 | return data->callback(file_path, data->callback_payload); |
00571828 VM |
626 | } |
627 | ||
87d3acf4 | 628 | static int _dirent_loose_load(void *data, char *full_path) |
2f8a8ab2 | 629 | { |
87d3acf4 VM |
630 | git_repository *repository = (git_repository *)data; |
631 | git_reference *reference, *old_ref; | |
632 | char *file_path; | |
633 | int error; | |
86194b24 | 634 | |
87d3acf4 | 635 | if (gitfo_isdir(full_path) == GIT_SUCCESS) |
0594e3ef | 636 | return gitfo_dirent(full_path, GIT_PATH_MAX, _dirent_loose_load, repository); |
9282e921 | 637 | |
87d3acf4 VM |
638 | file_path = full_path + strlen(repository->path_repository); |
639 | error = loose_lookup(&reference, repository, file_path, 1); | |
640 | if (error == GIT_SUCCESS && reference != NULL) { | |
641 | reference->type |= GIT_REF_PACKED; | |
86194b24 | 642 | |
87d3acf4 VM |
643 | if (git_hashtable_insert2(repository->references.packfile, reference->name, reference, (void **)&old_ref) < GIT_SUCCESS) { |
644 | reference_free(reference); | |
645 | return GIT_ENOMEM; | |
646 | } | |
647 | ||
648 | if (old_ref != NULL) | |
649 | reference_free(old_ref); | |
650 | } | |
9282e921 | 651 | |
5bdf7b9f | 652 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load loose dirent"); |
2f8a8ab2 VM |
653 | } |
654 | ||
87d3acf4 VM |
655 | /* |
656 | * Load all the loose references from the repository | |
657 | * into the in-memory Packfile, and build a vector with | |
658 | * all the references so it can be written back to | |
659 | * disk. | |
660 | */ | |
661 | static int packed_loadloose(git_repository *repository) | |
2f8a8ab2 | 662 | { |
87d3acf4 | 663 | char refs_path[GIT_PATH_MAX]; |
86194b24 | 664 | |
87d3acf4 VM |
665 | /* the packfile must have been previously loaded! */ |
666 | assert(repository->references.packfile); | |
9282e921 | 667 | |
87d3acf4 | 668 | git__joinpath(refs_path, repository->path_repository, GIT_REFS_DIR); |
86194b24 | 669 | |
87d3acf4 VM |
670 | /* Remove any loose references from the cache */ |
671 | { | |
402a47a7 | 672 | const void *GIT_UNUSED(_unused); |
87d3acf4 | 673 | git_reference *reference; |
9282e921 | 674 | |
87d3acf4 VM |
675 | GIT_HASHTABLE_FOREACH(repository->references.loose_cache, _unused, reference, |
676 | reference_free(reference); | |
677 | ); | |
678 | } | |
679 | ||
680 | git_hashtable_clear(repository->references.loose_cache); | |
681 | ||
682 | /* | |
683 | * Load all the loose files from disk into the Packfile table. | |
684 | * This will overwrite any old packed entries with their | |
685 | * updated loose versions | |
686 | */ | |
687 | return gitfo_dirent(refs_path, GIT_PATH_MAX, _dirent_loose_load, repository); | |
2f8a8ab2 | 688 | } |
9282e921 | 689 | |
87d3acf4 VM |
690 | /* |
691 | * Write a single reference into a packfile | |
692 | */ | |
693 | static int packed_write_ref(reference_oid *ref, git_filebuf *file) | |
2f8a8ab2 | 694 | { |
87d3acf4 VM |
695 | int error; |
696 | char oid[GIT_OID_HEXSZ + 1]; | |
2f8a8ab2 | 697 | |
87d3acf4 VM |
698 | git_oid_fmt(oid, &ref->oid); |
699 | oid[GIT_OID_HEXSZ] = 0; | |
2f8a8ab2 | 700 | |
87d3acf4 VM |
701 | /* |
702 | * For references that peel to an object in the repo, we must | |
703 | * write the resulting peel on a separate line, e.g. | |
704 | * | |
705 | * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4 | |
706 | * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100 | |
707 | * | |
708 | * This obviously only applies to tags. | |
709 | * The required peels have already been loaded into `ref->peel_target`. | |
710 | */ | |
711 | if (ref->ref.type & GIT_REF_HAS_PEEL) { | |
712 | char peel[GIT_OID_HEXSZ + 1]; | |
713 | git_oid_fmt(peel, &ref->peel_target); | |
714 | peel[GIT_OID_HEXSZ] = 0; | |
715 | ||
716 | error = git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->ref.name, peel); | |
717 | } else { | |
718 | error = git_filebuf_printf(file, "%s %s\n", oid, ref->ref.name); | |
719 | } | |
720 | ||
5bdf7b9f | 721 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference"); |
9282e921 | 722 | } |
723 | ||
87d3acf4 VM |
724 | /* |
725 | * Find out what object this reference resolves to. | |
726 | * | |
727 | * For references that point to a 'big' tag (e.g. an | |
728 | * actual tag object on the repository), we need to | |
729 | * cache on the packfile the OID of the object to | |
730 | * which that 'big tag' is pointing to. | |
731 | */ | |
732 | static int packed_find_peel(reference_oid *ref) | |
9282e921 | 733 | { |
d79f1da6 | 734 | git_object *object; |
87d3acf4 | 735 | int error; |
32054c24 | 736 | |
87d3acf4 VM |
737 | if (ref->ref.type & GIT_REF_HAS_PEEL) |
738 | return GIT_SUCCESS; | |
9282e921 | 739 | |
87d3acf4 VM |
740 | /* |
741 | * Only applies to tags, i.e. references | |
742 | * in the /refs/tags folder | |
743 | */ | |
744 | if (git__prefixcmp(ref->ref.name, GIT_REFS_TAGS_DIR) != 0) | |
745 | return GIT_SUCCESS; | |
9282e921 | 746 | |
87d3acf4 | 747 | /* |
d79f1da6 | 748 | * Find the tagged object in the repository |
87d3acf4 | 749 | */ |
d79f1da6 | 750 | error = git_object_lookup(&object, ref->ref.owner, &ref->oid, GIT_OBJ_ANY); |
87d3acf4 | 751 | if (error < GIT_SUCCESS) |
5bdf7b9f | 752 | return git__throw(GIT_EOBJCORRUPTED, "Failed to find packed reference"); |
86194b24 | 753 | |
87d3acf4 | 754 | /* |
d79f1da6 VM |
755 | * If the tagged object is a Tag object, we need to resolve it; |
756 | * if the ref is actually a 'weak' ref, we don't need to resolve | |
757 | * anything. | |
87d3acf4 | 758 | */ |
d79f1da6 VM |
759 | if (git_object_type(object) == GIT_OBJ_TAG) { |
760 | git_tag *tag = (git_tag *)object; | |
86194b24 | 761 | |
d79f1da6 VM |
762 | /* |
763 | * Find the object pointed at by this tag | |
764 | */ | |
765 | git_oid_cpy(&ref->peel_target, git_tag_target_oid(tag)); | |
766 | ref->ref.type |= GIT_REF_HAS_PEEL; | |
767 | ||
768 | /* | |
769 | * The reference has now cached the resolved OID, and is | |
770 | * marked at such. When written to the packfile, it'll be | |
771 | * accompanied by this resolved oid | |
772 | */ | |
773 | } | |
87d3acf4 | 774 | |
1648fbd3 VM |
775 | git_object_close(object); |
776 | ||
87d3acf4 | 777 | return GIT_SUCCESS; |
2f8a8ab2 | 778 | } |
9282e921 | 779 | |
87d3acf4 VM |
780 | /* |
781 | * Remove all loose references | |
782 | * | |
783 | * Once we have successfully written a packfile, | |
784 | * all the loose references that were packed must be | |
785 | * removed from disk. | |
786 | * | |
787 | * This is a dangerous method; make sure the packfile | |
788 | * is well-written, because we are destructing references | |
789 | * here otherwise. | |
790 | */ | |
791 | static int packed_remove_loose(git_repository *repo, git_vector *packing_list) | |
2f8a8ab2 | 792 | { |
87d3acf4 VM |
793 | unsigned int i; |
794 | char full_path[GIT_PATH_MAX]; | |
795 | int error = GIT_SUCCESS; | |
8f90ced5 | 796 | git_reference *reference; |
87d3acf4 VM |
797 | |
798 | for (i = 0; i < packing_list->length; ++i) { | |
799 | git_reference *ref = git_vector_get(packing_list, i); | |
8f90ced5 | 800 | |
801 | /* Ensure the packed reference doesn't exist | |
802 | * in a (more up-to-date?) state as a loose reference | |
803 | */ | |
804 | reference = git_hashtable_lookup(ref->owner->references.loose_cache, ref->name); | |
805 | if (reference != NULL) | |
806 | continue; | |
807 | ||
87d3acf4 VM |
808 | git__joinpath(full_path, repo->path_repository, ref->name); |
809 | ||
810 | if (gitfo_exists(full_path) == GIT_SUCCESS && | |
811 | gitfo_unlink(full_path) < GIT_SUCCESS) | |
812 | error = GIT_EOSERR; | |
813 | ||
814 | /* | |
815 | * if we fail to remove a single file, this is *not* good, | |
816 | * but we should keep going and remove as many as possible. | |
817 | * After we've removed as many files as possible, we return | |
818 | * the error code anyway. | |
819 | * | |
820 | * TODO: mark this with a very special error code? | |
821 | * GIT_EFAILTORMLOOSE | |
822 | */ | |
823 | } | |
824 | ||
5bdf7b9f | 825 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to remove loose packed reference"); |
2f8a8ab2 | 826 | } |
9282e921 | 827 | |
87d3acf4 | 828 | static int packed_sort(const void *a, const void *b) |
2f8a8ab2 | 829 | { |
87d3acf4 VM |
830 | const git_reference *ref_a = *(const git_reference **)a; |
831 | const git_reference *ref_b = *(const git_reference **)b; | |
832 | ||
833 | return strcmp(ref_a->name, ref_b->name); | |
2f8a8ab2 VM |
834 | } |
835 | ||
87d3acf4 VM |
836 | /* |
837 | * Write all the contents in the in-memory packfile to disk. | |
838 | */ | |
839 | static int packed_write(git_repository *repo) | |
2f8a8ab2 | 840 | { |
87d3acf4 VM |
841 | git_filebuf pack_file; |
842 | int error; | |
843 | unsigned int i; | |
844 | char pack_file_path[GIT_PATH_MAX]; | |
2f8a8ab2 | 845 | |
87d3acf4 VM |
846 | git_vector packing_list; |
847 | size_t total_refs; | |
2f8a8ab2 | 848 | |
87d3acf4 | 849 | assert(repo && repo->references.packfile); |
9282e921 | 850 | |
87d3acf4 | 851 | total_refs = repo->references.packfile->key_count; |
86d7e1ca | 852 | if ((error = git_vector_init(&packing_list, total_refs, packed_sort)) < GIT_SUCCESS) |
5bdf7b9f | 853 | return git__rethrow(error, "Failed to write packed reference"); |
9282e921 | 854 | |
87d3acf4 VM |
855 | /* Load all the packfile into a vector */ |
856 | { | |
857 | git_reference *reference; | |
402a47a7 | 858 | const void *GIT_UNUSED(_unused); |
87d3acf4 VM |
859 | |
860 | GIT_HASHTABLE_FOREACH(repo->references.packfile, _unused, reference, | |
861 | git_vector_insert(&packing_list, reference); /* cannot fail: vector already has the right size */ | |
862 | ); | |
9282e921 | 863 | } |
864 | ||
87d3acf4 VM |
865 | /* sort the vector so the entries appear sorted on the packfile */ |
866 | git_vector_sort(&packing_list); | |
9282e921 | 867 | |
87d3acf4 VM |
868 | /* Now we can open the file! */ |
869 | git__joinpath(pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE); | |
870 | if ((error = git_filebuf_open(&pack_file, pack_file_path, 0)) < GIT_SUCCESS) | |
5bdf7b9f | 871 | return git__rethrow(error, "Failed to write packed reference"); |
9282e921 | 872 | |
7c8a7b91 VM |
873 | /* Packfiles have a header... apparently |
874 | * This is in fact not required, but we might as well print it | |
875 | * just for kicks */ | |
87d3acf4 | 876 | if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < GIT_SUCCESS) |
5bdf7b9f | 877 | return git__rethrow(error, "Failed to write packed reference"); |
2f8a8ab2 | 878 | |
87d3acf4 VM |
879 | for (i = 0; i < packing_list.length; ++i) { |
880 | reference_oid *ref = (reference_oid *)git_vector_get(&packing_list, i); | |
2f8a8ab2 | 881 | |
87d3acf4 VM |
882 | /* only direct references go to the packfile; otherwise |
883 | * this is a disaster */ | |
884 | assert(ref->ref.type & GIT_REF_OID); | |
2f8a8ab2 | 885 | |
2b397327 VM |
886 | if ((error = packed_find_peel(ref)) < GIT_SUCCESS) { |
887 | error = git__throw(GIT_EOBJCORRUPTED, "A reference cannot be peeled"); | |
87d3acf4 | 888 | goto cleanup; |
2b397327 VM |
889 | } |
890 | ||
2f8a8ab2 | 891 | |
87d3acf4 VM |
892 | if ((error = packed_write_ref(ref, &pack_file)) < GIT_SUCCESS) |
893 | goto cleanup; | |
894 | } | |
2f8a8ab2 | 895 | |
87d3acf4 VM |
896 | cleanup: |
897 | /* if we've written all the references properly, we can commit | |
898 | * the packfile to make the changes effective */ | |
899 | if (error == GIT_SUCCESS) { | |
900 | error = git_filebuf_commit(&pack_file); | |
901 | ||
902 | /* when and only when the packfile has been properly written, | |
903 | * we can go ahead and remove the loose refs */ | |
7341bf87 VM |
904 | if (error == GIT_SUCCESS) { |
905 | struct stat st; | |
906 | ||
87d3acf4 | 907 | error = packed_remove_loose(repo, &packing_list); |
7341bf87 VM |
908 | |
909 | if (gitfo_stat(pack_file_path, &st) == GIT_SUCCESS) | |
910 | repo->references.packfile_time = st.st_mtime; | |
911 | } | |
9282e921 | 912 | } |
87d3acf4 | 913 | else git_filebuf_cleanup(&pack_file); |
9282e921 | 914 | |
87d3acf4 | 915 | git_vector_free(&packing_list); |
1d8cc731 | 916 | |
5bdf7b9f | 917 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference"); |
87d3acf4 | 918 | } |
2f8a8ab2 | 919 | |
1b6d8163 MS |
920 | static int _reference_available_cb(const char *ref, void *data) |
921 | { | |
f120e92b | 922 | const char *new, *old; |
923 | git_vector *refs; | |
924 | ||
1b6d8163 MS |
925 | assert(ref && data); |
926 | ||
f120e92b | 927 | refs = (git_vector *)data; |
1b6d8163 | 928 | |
f120e92b | 929 | new = (const char *)git_vector_get(refs, 0); |
930 | old = (const char *)git_vector_get(refs, 1); | |
1b6d8163 MS |
931 | |
932 | if (!old || strcmp(old, ref)) { | |
933 | int reflen = strlen(ref); | |
934 | int newlen = strlen(new); | |
935 | int cmplen = reflen < newlen ? reflen : newlen; | |
936 | const char *lead = reflen < newlen ? new : ref; | |
937 | ||
938 | if (!strncmp(new, ref, cmplen) && | |
939 | lead[cmplen] == '/') | |
940 | return GIT_EEXISTS; | |
941 | } | |
942 | ||
943 | return GIT_SUCCESS; | |
944 | } | |
945 | ||
946 | static int reference_available(git_repository *repo, const char *ref, const char* old_ref) | |
947 | { | |
948 | int error; | |
949 | git_vector refs; | |
950 | ||
951 | if (git_vector_init(&refs, 2, NULL) < GIT_SUCCESS) | |
952 | return GIT_ENOMEM; | |
953 | ||
954 | git_vector_insert(&refs, (void *)ref); | |
955 | git_vector_insert(&refs, (void *)old_ref); | |
956 | ||
43521d06 | 957 | error = git_reference_foreach(repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&refs); |
1b6d8163 MS |
958 | |
959 | git_vector_free(&refs); | |
960 | ||
76b15cb1 | 961 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__throw(GIT_EEXISTS, "Reference name `%s` conflicts with existing reference", ref); |
1b6d8163 MS |
962 | } |
963 | ||
87d3acf4 VM |
964 | /***************************************** |
965 | * External Library API | |
966 | *****************************************/ | |
967 | ||
968 | /** | |
969 | * Constructors | |
970 | */ | |
5de079b8 | 971 | int git_reference_lookup(git_reference **ref_out, git_repository *repo, const char *name) |
2f8a8ab2 VM |
972 | { |
973 | int error; | |
3101a3e5 | 974 | char normalized_name[GIT_REFNAME_MAX]; |
2f8a8ab2 VM |
975 | |
976 | assert(ref_out && repo && name); | |
977 | ||
978 | *ref_out = NULL; | |
979 | ||
3101a3e5 | 980 | error = normalize_name(normalized_name, sizeof(normalized_name), name, 0); |
2f8a8ab2 | 981 | if (error < GIT_SUCCESS) |
5bdf7b9f | 982 | return git__rethrow(error, "Failed to lookup reference"); |
2f8a8ab2 | 983 | |
87d3acf4 VM |
984 | /* First, check has been previously loaded and cached */ |
985 | *ref_out = git_hashtable_lookup(repo->references.loose_cache, normalized_name); | |
2f8a8ab2 | 986 | if (*ref_out != NULL) |
7341bf87 | 987 | return loose_update(*ref_out); |
2f8a8ab2 | 988 | |
87d3acf4 VM |
989 | /* Then check if there is a loose file for that reference.*/ |
990 | error = loose_lookup(ref_out, repo, normalized_name, 0); | |
2f8a8ab2 | 991 | |
87d3acf4 | 992 | /* If the file exists, we store it on the cache */ |
2f8a8ab2 | 993 | if (error == GIT_SUCCESS) |
87d3acf4 | 994 | return git_hashtable_insert(repo->references.loose_cache, (*ref_out)->name, (*ref_out)); |
2f8a8ab2 | 995 | |
87d3acf4 VM |
996 | /* The loose lookup has failed, but not because the reference wasn't found; |
997 | * probably the loose reference is corrupted. this is bad. */ | |
9282e921 | 998 | if (error != GIT_ENOTFOUND) |
5bdf7b9f | 999 | return git__rethrow(error, "Failed to lookup reference"); |
9282e921 | 1000 | |
87d3acf4 VM |
1001 | /* |
1002 | * If we cannot find a loose reference, we look into the packfile | |
1003 | * Load the packfile first if it hasn't been loaded | |
1004 | */ | |
7341bf87 VM |
1005 | /* load all the packed references */ |
1006 | error = packed_load(repo); | |
1007 | if (error < GIT_SUCCESS) | |
5bdf7b9f | 1008 | return git__rethrow(error, "Failed to lookup reference"); |
9282e921 | 1009 | |
87d3acf4 VM |
1010 | /* Look up on the packfile */ |
1011 | *ref_out = git_hashtable_lookup(repo->references.packfile, normalized_name); | |
86194b24 VM |
1012 | if (*ref_out != NULL) |
1013 | return GIT_SUCCESS; | |
1014 | ||
2f8a8ab2 | 1015 | /* The reference doesn't exist anywhere */ |
5bdf7b9f | 1016 | return git__throw(GIT_ENOTFOUND, "Failed to lookup reference. Reference doesn't exist"); |
2f8a8ab2 | 1017 | } |
9282e921 | 1018 | |
87d3acf4 VM |
1019 | /** |
1020 | * Getters | |
1021 | */ | |
1022 | git_rtype git_reference_type(git_reference *ref) | |
1023 | { | |
1024 | assert(ref); | |
1025 | ||
1026 | if (ref->type & GIT_REF_OID) | |
1027 | return GIT_REF_OID; | |
1028 | ||
1029 | if (ref->type & GIT_REF_SYMBOLIC) | |
1030 | return GIT_REF_SYMBOLIC; | |
1031 | ||
1032 | return GIT_REF_INVALID; | |
1033 | } | |
1034 | ||
1035 | const char *git_reference_name(git_reference *ref) | |
1036 | { | |
1037 | assert(ref); | |
1038 | return ref->name; | |
1039 | } | |
1040 | ||
1041 | git_repository *git_reference_owner(git_reference *ref) | |
1042 | { | |
1043 | assert(ref); | |
1044 | return ref->owner; | |
1045 | } | |
1046 | ||
1047 | const git_oid *git_reference_oid(git_reference *ref) | |
1048 | { | |
1049 | assert(ref); | |
1050 | ||
1051 | if ((ref->type & GIT_REF_OID) == 0) | |
1052 | return NULL; | |
1053 | ||
7341bf87 VM |
1054 | if (loose_update(ref) < GIT_SUCCESS) |
1055 | return NULL; | |
1056 | ||
87d3acf4 VM |
1057 | return &((reference_oid *)ref)->oid; |
1058 | } | |
1059 | ||
1060 | const char *git_reference_target(git_reference *ref) | |
1061 | { | |
1062 | assert(ref); | |
1063 | ||
1064 | if ((ref->type & GIT_REF_SYMBOLIC) == 0) | |
1065 | return NULL; | |
1066 | ||
7341bf87 VM |
1067 | if (loose_update(ref) < GIT_SUCCESS) |
1068 | return NULL; | |
1069 | ||
87d3acf4 VM |
1070 | return ((reference_symbolic *)ref)->target; |
1071 | } | |
1072 | ||
d5afc039 VM |
1073 | int git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force) |
1074 | { | |
1075 | char normalized[GIT_REFNAME_MAX]; | |
1076 | int error = GIT_SUCCESS, updated = 0; | |
1077 | git_reference *ref = NULL, *old_ref = NULL; | |
1078 | ||
1079 | if (git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force) | |
1080 | return git__throw(GIT_EEXISTS, "Failed to create symbolic reference. Reference already exists"); | |
1081 | ||
1082 | /* | |
1083 | * If they old ref was of the same type, then we can just update | |
1084 | * it (once we've checked that the target is valid). Otherwise we | |
1085 | * need a new reference because we can't make a symbolic ref out | |
1086 | * of an oid one. | |
1087 | * If if didn't exist, then we need to create a new one anyway. | |
1088 | */ | |
1089 | if (ref && ref->type & GIT_REF_SYMBOLIC){ | |
1090 | updated = 1; | |
1091 | } else { | |
1092 | ref = NULL; | |
1093 | error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC); | |
1094 | if (error < GIT_SUCCESS) | |
1095 | goto cleanup; | |
1096 | } | |
1097 | ||
1098 | /* The target can aither be the name of an object id reference or the name of another symbolic reference */ | |
1099 | error = normalize_name(normalized, sizeof(normalized), target, 0); | |
1100 | if (error < GIT_SUCCESS) | |
1101 | goto cleanup; | |
1102 | ||
1103 | /* set the target; this will write the reference on disk */ | |
1104 | error = git_reference_set_target(ref, normalized); | |
1105 | if (error < GIT_SUCCESS) | |
1106 | goto cleanup; | |
1107 | ||
1108 | /* | |
1109 | * If we didn't update the ref, then we need to insert or replace | |
1110 | * it in the loose cache. If we replaced a ref, free it. | |
1111 | */ | |
1112 | if (!updated){ | |
1113 | error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, (void **) &old_ref); | |
1114 | if (error < GIT_SUCCESS) | |
1115 | goto cleanup; | |
1116 | ||
1117 | if(old_ref) | |
1118 | reference_free(old_ref); | |
1119 | } | |
1120 | ||
1121 | *ref_out = ref; | |
1122 | ||
1123 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference"); | |
1124 | ||
1125 | cleanup: | |
1126 | reference_free(ref); | |
1127 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference"); | |
1128 | } | |
1129 | ||
1130 | int git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force) | |
1131 | { | |
1132 | int error = GIT_SUCCESS, updated = 0; | |
1133 | git_reference *ref = NULL, *old_ref = NULL; | |
1134 | ||
1135 | if(git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force) | |
1136 | return git__throw(GIT_EEXISTS, "Failed to create reference OID. Reference already exists"); | |
1137 | ||
1138 | if ((error = reference_available(repo, name, NULL)) < GIT_SUCCESS) | |
1139 | return git__rethrow(error, "Failed to create reference"); | |
1140 | ||
1141 | /* | |
1142 | * If they old ref was of the same type, then we can just update | |
1143 | * it (once we've checked that the target is valid). Otherwise we | |
1144 | * need a new reference because we can't make a symbolic ref out | |
1145 | * of an oid one. | |
1146 | * If if didn't exist, then we need to create a new one anyway. | |
1147 | */ | |
1148 | if (ref && ref-> type & GIT_REF_OID){ | |
1149 | updated = 1; | |
1150 | } else { | |
1151 | ref = NULL; | |
1152 | error = reference_create(&ref, repo, name, GIT_REF_OID); | |
1153 | if (error < GIT_SUCCESS) | |
1154 | goto cleanup; | |
1155 | } | |
1156 | ||
1157 | /* set the oid; this will write the reference on disk */ | |
1158 | error = git_reference_set_oid(ref, id); | |
1159 | if (error < GIT_SUCCESS) | |
1160 | goto cleanup; | |
1161 | ||
1162 | if(!updated){ | |
1163 | error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, (void **) &old_ref); | |
1164 | if (error < GIT_SUCCESS) | |
1165 | goto cleanup; | |
1166 | ||
1167 | if(old_ref) | |
1168 | reference_free(old_ref); | |
1169 | } | |
1170 | ||
1171 | *ref_out = ref; | |
1172 | ||
1173 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID"); | |
1174 | ||
1175 | cleanup: | |
1176 | reference_free(ref); | |
1177 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID"); | |
1178 | } | |
1179 | ||
87d3acf4 VM |
1180 | /** |
1181 | * Setters | |
1182 | */ | |
1183 | ||
1184 | /* | |
1185 | * Change the OID target of a reference. | |
1186 | * | |
1187 | * For loose references, just change the oid in memory | |
1188 | * and overwrite the file in disk. | |
1189 | * | |
1190 | * For packed files, this is not pretty: | |
1191 | * For performance reasons, we write the new reference | |
1192 | * loose on disk (it replaces the old on the packfile), | |
1193 | * but we cannot invalidate the pointer to the reference, | |
1194 | * and most importantly, the `packfile` object must stay | |
1195 | * consistent with the representation of the packfile | |
1196 | * on disk. This is what we need to: | |
1197 | * | |
1198 | * 1. Copy the reference | |
1199 | * 2. Change the oid on the original | |
1200 | * 3. Write the original to disk | |
1201 | * 4. Write the original to the loose cache | |
1202 | * 5. Replace the original with the copy (old reference) in the packfile cache | |
1203 | */ | |
1204 | int git_reference_set_oid(git_reference *ref, const git_oid *id) | |
1205 | { | |
1206 | reference_oid *ref_oid; | |
1207 | reference_oid *ref_old = NULL; | |
1208 | int error = GIT_SUCCESS; | |
1209 | ||
1210 | if ((ref->type & GIT_REF_OID) == 0) | |
5bdf7b9f | 1211 | return git__throw(GIT_EINVALIDREFSTATE, "Failed to set OID target of reference. Not an OID reference"); |
87d3acf4 VM |
1212 | |
1213 | ref_oid = (reference_oid *)ref; | |
1214 | ||
9a53df7e VM |
1215 | assert(ref->owner); |
1216 | ||
1217 | /* Don't let the user create references to OIDs that | |
1218 | * don't exist in the ODB */ | |
1219 | if (!git_odb_exists(git_repository_database(ref->owner), id)) | |
5bdf7b9f | 1220 | return git__throw(GIT_ENOTFOUND, "Failed to set OID target of reference. OID doesn't exist in ODB"); |
9a53df7e | 1221 | |
87d3acf4 VM |
1222 | /* duplicate the reference; |
1223 | * this copy will stay on the packfile cache */ | |
1224 | if (ref->type & GIT_REF_PACKED) { | |
1225 | ref_old = git__malloc(sizeof(reference_oid)); | |
1226 | if (ref_old == NULL) | |
1227 | return GIT_ENOMEM; | |
1228 | ||
1229 | ref_old->ref.name = git__strdup(ref->name); | |
1230 | if (ref_old->ref.name == NULL) { | |
1231 | free(ref_old); | |
1232 | return GIT_ENOMEM; | |
1233 | } | |
1234 | } | |
1235 | ||
1236 | git_oid_cpy(&ref_oid->oid, id); | |
1237 | ref->type &= ~GIT_REF_HAS_PEEL; | |
1238 | ||
1239 | error = loose_write(ref); | |
1240 | if (error < GIT_SUCCESS) | |
1241 | goto cleanup; | |
1242 | ||
1243 | if (ref->type & GIT_REF_PACKED) { | |
1244 | /* insert the original on the loose cache */ | |
1245 | error = git_hashtable_insert(ref->owner->references.loose_cache, ref->name, ref); | |
1246 | if (error < GIT_SUCCESS) | |
1247 | goto cleanup; | |
1248 | ||
1249 | ref->type &= ~GIT_REF_PACKED; | |
1250 | ||
1251 | /* replace the original in the packfile with the copy */ | |
1252 | error = git_hashtable_insert(ref->owner->references.packfile, ref_old->ref.name, ref_old); | |
1253 | if (error < GIT_SUCCESS) | |
1254 | goto cleanup; | |
1255 | } | |
1256 | ||
1257 | return GIT_SUCCESS; | |
1258 | ||
1259 | cleanup: | |
1260 | reference_free((git_reference *)ref_old); | |
5bdf7b9f | 1261 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set OID target of reference"); |
87d3acf4 VM |
1262 | } |
1263 | ||
1264 | /* | |
1265 | * Change the target of a symbolic reference. | |
1266 | * | |
1267 | * This is easy because symrefs cannot be inside | |
1268 | * a pack. We just change the target in memory | |
1269 | * and overwrite the file on disk. | |
1270 | */ | |
1271 | int git_reference_set_target(git_reference *ref, const char *target) | |
1272 | { | |
1273 | reference_symbolic *ref_sym; | |
1274 | ||
1275 | if ((ref->type & GIT_REF_SYMBOLIC) == 0) | |
5bdf7b9f | 1276 | return git__throw(GIT_EINVALIDREFSTATE, "Failed to set reference target. Not a symbolic reference"); |
87d3acf4 VM |
1277 | |
1278 | ref_sym = (reference_symbolic *)ref; | |
1279 | ||
1280 | free(ref_sym->target); | |
1281 | ref_sym->target = git__strdup(target); | |
1282 | if (ref_sym->target == NULL) | |
1283 | return GIT_ENOMEM; | |
1284 | ||
1285 | return loose_write(ref); | |
1286 | } | |
1287 | ||
1288 | /** | |
1289 | * Other | |
1290 | */ | |
1291 | ||
7376ad99 VM |
1292 | /* |
1293 | * Rename a reference | |
1294 | * | |
1295 | * If the reference is packed, we need to rewrite the | |
1296 | * packfile to remove the reference from it and create | |
1297 | * the reference back as a loose one. | |
1298 | * | |
1299 | * If the reference is loose, we just rename it on | |
1300 | * the filesystem. | |
1301 | * | |
1302 | * We also need to re-insert the reference on its corresponding | |
1303 | * in-memory cache, since the caches are indexed by refname. | |
1304 | */ | |
1305 | int git_reference_rename(git_reference *ref, const char *new_name, int force) | |
1306 | { | |
1307 | int error; | |
1308 | char *old_name; | |
1309 | char old_path[GIT_PATH_MAX], new_path[GIT_PATH_MAX], normalized_name[GIT_REFNAME_MAX]; | |
1310 | git_reference *looked_up_ref, *old_ref = NULL; | |
1311 | ||
1312 | assert(ref); | |
1313 | ||
1314 | /* Ensure the name is valid */ | |
1315 | error = normalize_name(normalized_name, sizeof(normalized_name), new_name, ref->type & GIT_REF_OID); | |
1316 | if (error < GIT_SUCCESS) | |
1317 | return git__rethrow(error, "Failed to rename reference"); | |
1318 | ||
1319 | new_name = normalized_name; | |
1320 | ||
1321 | /* Ensure we're not going to overwrite an existing reference | |
1322 | unless the user has allowed us */ | |
1323 | error = git_reference_lookup(&looked_up_ref, ref->owner, new_name); | |
1324 | if (error == GIT_SUCCESS && !force) | |
1325 | return git__throw(GIT_EEXISTS, "Failed to rename reference. Reference already exists"); | |
1326 | ||
1327 | if (error < GIT_SUCCESS && | |
1328 | error != GIT_ENOTFOUND) | |
1329 | return git__rethrow(error, "Failed to rename reference"); | |
1330 | ||
1331 | if ((error = reference_available(ref->owner, new_name, ref->name)) < GIT_SUCCESS) | |
1332 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference. Reference already exists"); | |
1333 | ||
1334 | old_name = ref->name; | |
1335 | ref->name = git__strdup(new_name); | |
1336 | ||
1337 | if (ref->name == NULL) { | |
1338 | ref->name = old_name; | |
1339 | return GIT_ENOMEM; | |
1340 | } | |
1341 | ||
1342 | if (ref->type & GIT_REF_PACKED) { | |
1343 | /* write the packfile to disk; note | |
1344 | * that the state of the in-memory cache is not | |
1345 | * consistent, because the reference is indexed | |
1346 | * by its old name but it already has the new one. | |
1347 | * This doesn't affect writing, though, and allows | |
1348 | * us to rollback if writing fails | |
1349 | */ | |
1350 | ||
1351 | ref->type &= ~GIT_REF_PACKED; | |
1352 | ||
1353 | /* Create the loose ref under its new name */ | |
1354 | error = loose_write(ref); | |
1355 | if (error < GIT_SUCCESS) { | |
1356 | ref->type |= GIT_REF_PACKED; | |
1357 | goto cleanup; | |
1358 | } | |
1359 | ||
1360 | /* Remove from the packfile cache in order to avoid packing it back | |
1361 | * Note : we do not rely on git_reference_delete() because this would | |
1362 | * invalidate the reference. | |
1363 | */ | |
1364 | git_hashtable_remove(ref->owner->references.packfile, old_name); | |
1365 | ||
1366 | /* Recreate the packed-refs file without the reference */ | |
1367 | error = packed_write(ref->owner); | |
1368 | if (error < GIT_SUCCESS) | |
1369 | goto rename_loose_to_old_name; | |
1370 | ||
1371 | } else { | |
1372 | git__joinpath(old_path, ref->owner->path_repository, old_name); | |
1373 | git__joinpath(new_path, ref->owner->path_repository, ref->name); | |
1374 | ||
1375 | error = gitfo_mv_force(old_path, new_path); | |
1376 | if (error < GIT_SUCCESS) | |
1377 | goto cleanup; | |
1378 | ||
1379 | /* Once succesfully renamed, remove from the cache the reference known by its old name*/ | |
1380 | git_hashtable_remove(ref->owner->references.loose_cache, old_name); | |
1381 | } | |
1382 | ||
1383 | /* Store the renamed reference into the loose ref cache */ | |
1384 | error = git_hashtable_insert2(ref->owner->references.loose_cache, ref->name, ref, (void **) &old_ref); | |
1385 | ||
1386 | /* If we force-replaced, we need to free the old reference */ | |
1387 | if(old_ref) | |
1388 | reference_free(old_ref); | |
1389 | ||
1390 | free(old_name); | |
1391 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference"); | |
1392 | ||
1393 | cleanup: | |
1394 | /* restore the old name if this failed */ | |
1395 | free(ref->name); | |
1396 | ref->name = old_name; | |
1397 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference"); | |
1398 | ||
1399 | rename_loose_to_old_name: | |
1400 | /* If we hit this point. Something *bad* happened! Think "Ghostbusters | |
1401 | * crossing the streams" definition of bad. | |
1402 | * Either the packed-refs has been correctly generated and something else | |
1403 | * has gone wrong, or the writing of the new packed-refs has failed, and | |
1404 | * we're stuck with the old one. As a loose ref always takes priority over | |
1405 | * a packed ref, we'll eventually try and rename the generated loose ref to | |
1406 | * its former name. It even that fails, well... we might have lost the reference | |
1407 | * for good. :-/ | |
1408 | */ | |
1409 | ||
1410 | git__joinpath(old_path, ref->owner->path_repository, ref->name); | |
1411 | git__joinpath(new_path, ref->owner->path_repository, old_name); | |
1412 | ||
1413 | /* No error checking. We'll return the initial error */ | |
1414 | gitfo_mv_force(old_path, new_path); | |
1415 | ||
1416 | /* restore the old name */ | |
1417 | free(ref->name); | |
1418 | ref->name = old_name; | |
1419 | ||
1420 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference"); | |
1421 | } | |
1422 | ||
1423 | ||
87d3acf4 VM |
1424 | /* |
1425 | * Delete a reference. | |
1426 | * | |
1427 | * If the reference is packed, this is an expensive | |
1428 | * operation. We need to remove the reference from | |
1429 | * the memory cache and then rewrite the whole pack | |
1430 | * | |
5ad0351d | 1431 | * If the reference is loose, we remove it on |
87d3acf4 | 1432 | * the filesystem and update the in-memory cache |
5ad0351d | 1433 | * accordingly. We also make sure that an older version |
1434 | * of it doesn't exist as a packed reference. If this | |
1435 | * is the case, this packed reference is removed as well. | |
87d3acf4 VM |
1436 | * |
1437 | * This obviously invalidates the `ref` pointer. | |
1438 | */ | |
1439 | int git_reference_delete(git_reference *ref) | |
1440 | { | |
1441 | int error; | |
5ad0351d | 1442 | git_reference *reference; |
87d3acf4 VM |
1443 | |
1444 | assert(ref); | |
1445 | ||
1446 | if (ref->type & GIT_REF_PACKED) { | |
d79f1da6 VM |
1447 | /* load the existing packfile */ |
1448 | if ((error = packed_load(ref->owner)) < GIT_SUCCESS) | |
5bdf7b9f | 1449 | return git__rethrow(error, "Failed to delete reference"); |
d79f1da6 | 1450 | |
2b397327 VM |
1451 | if (git_hashtable_remove(ref->owner->references.packfile, ref->name) < GIT_SUCCESS) |
1452 | return git__throw(GIT_ENOTFOUND, "Reference not found"); | |
1453 | ||
87d3acf4 VM |
1454 | error = packed_write(ref->owner); |
1455 | } else { | |
1456 | char full_path[GIT_PATH_MAX]; | |
1457 | git__joinpath(full_path, ref->owner->path_repository, ref->name); | |
1458 | git_hashtable_remove(ref->owner->references.loose_cache, ref->name); | |
1459 | error = gitfo_unlink(full_path); | |
5ad0351d | 1460 | if (error < GIT_SUCCESS) |
1461 | goto cleanup; | |
1462 | ||
1463 | /* When deleting a loose reference, we have to ensure that an older | |
1464 | * packed version of it doesn't exist | |
1465 | */ | |
5de079b8 | 1466 | if (!git_reference_lookup(&reference, ref->owner, ref->name)) { |
5ad0351d | 1467 | assert((reference->type & GIT_REF_PACKED) != 0); |
1468 | error = git_reference_delete(reference); | |
1469 | } | |
87d3acf4 VM |
1470 | } |
1471 | ||
5ad0351d | 1472 | cleanup: |
87d3acf4 | 1473 | reference_free(ref); |
5bdf7b9f | 1474 | return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to delete reference"); |
87d3acf4 VM |
1475 | } |
1476 | ||
87d3acf4 VM |
1477 | int git_reference_resolve(git_reference **resolved_ref, git_reference *ref) |
1478 | { | |
1479 | git_repository *repo; | |
1480 | int error, i; | |
1481 | ||
1482 | assert(resolved_ref && ref); | |
1483 | *resolved_ref = NULL; | |
7341bf87 VM |
1484 | |
1485 | if ((error = loose_update(ref)) < GIT_SUCCESS) | |
5bdf7b9f | 1486 | return git__rethrow(error, "Failed to resolve reference"); |
87d3acf4 VM |
1487 | |
1488 | repo = ref->owner; | |
1489 | ||
1490 | for (i = 0; i < MAX_NESTING_LEVEL; ++i) { | |
1491 | reference_symbolic *ref_sym; | |
1492 | ||
68a146c1 CMN |
1493 | *resolved_ref = ref; |
1494 | ||
0d5d5190 | 1495 | if (ref->type & GIT_REF_OID) |
87d3acf4 | 1496 | return GIT_SUCCESS; |
87d3acf4 VM |
1497 | |
1498 | ref_sym = (reference_symbolic *)ref; | |
5de079b8 | 1499 | if ((error = git_reference_lookup(&ref, repo, ref_sym->target)) < GIT_SUCCESS) |
0d5d5190 | 1500 | return error; |
87d3acf4 VM |
1501 | } |
1502 | ||
0d5d5190 | 1503 | return git__throw(GIT_ENOMEM, "Failed to resolve reference. Reference is too nested"); |
87d3acf4 VM |
1504 | } |
1505 | ||
1506 | int git_reference_packall(git_repository *repo) | |
1507 | { | |
1508 | int error; | |
1509 | ||
1510 | /* load the existing packfile */ | |
1511 | if ((error = packed_load(repo)) < GIT_SUCCESS) | |
5bdf7b9f | 1512 | return git__rethrow(error, "Failed to pack references"); |
87d3acf4 VM |
1513 | |
1514 | /* update it in-memory with all the loose references */ | |
1515 | if ((error = packed_loadloose(repo)) < GIT_SUCCESS) | |
5bdf7b9f | 1516 | return git__rethrow(error, "Failed to pack references"); |
87d3acf4 VM |
1517 | |
1518 | /* write it back to disk */ | |
1519 | return packed_write(repo); | |
1520 | } | |
1521 | ||
43521d06 | 1522 | int git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload) |
00571828 VM |
1523 | { |
1524 | int error; | |
1525 | struct dirent_list_data data; | |
1526 | char refs_path[GIT_PATH_MAX]; | |
1527 | ||
7ad96e51 | 1528 | /* list all the packed references first */ |
00571828 VM |
1529 | if (list_flags & GIT_REF_PACKED) { |
1530 | const char *ref_name; | |
402a47a7 | 1531 | void *GIT_UNUSED(_unused); |
00571828 | 1532 | |
09e8de0f | 1533 | if ((error = packed_load(repo)) < GIT_SUCCESS) |
5bdf7b9f | 1534 | return git__rethrow(error, "Failed to list references"); |
00571828 VM |
1535 | |
1536 | GIT_HASHTABLE_FOREACH(repo->references.packfile, ref_name, _unused, | |
09e8de0f | 1537 | if ((error = callback(ref_name, payload)) < GIT_SUCCESS) |
0d5d5190 | 1538 | return git__throw(error, "Failed to list references. User callback failed"); |
00571828 VM |
1539 | ); |
1540 | } | |
1541 | ||
7ad96e51 VM |
1542 | /* now list the loose references, trying not to |
1543 | * duplicate the ref names already in the packed-refs file */ | |
09e8de0f VM |
1544 | |
1545 | data.repo_path_len = strlen(repo->path_repository); | |
1546 | data.list_flags = list_flags; | |
1547 | data.repo = repo; | |
1548 | data.callback = callback; | |
1549 | data.callback_payload = payload; | |
1550 | ||
1551 | ||
7ad96e51 | 1552 | git__joinpath(refs_path, repo->path_repository, GIT_REFS_DIR); |
09e8de0f VM |
1553 | return gitfo_dirent(refs_path, GIT_PATH_MAX, _dirent_loose_listall, &data); |
1554 | } | |
1555 | ||
1556 | int cb__reflist_add(const char *ref, void *data) | |
1557 | { | |
1558 | return git_vector_insert((git_vector *)data, git__strdup(ref)); | |
1559 | } | |
1560 | ||
1561 | int git_reference_listall(git_strarray *array, git_repository *repo, unsigned int list_flags) | |
1562 | { | |
1563 | int error; | |
1564 | git_vector ref_list; | |
1565 | ||
1566 | assert(array && repo); | |
1567 | ||
1568 | array->strings = NULL; | |
1569 | array->count = 0; | |
1570 | ||
1571 | if (git_vector_init(&ref_list, 8, NULL) < GIT_SUCCESS) | |
1572 | return GIT_ENOMEM; | |
1573 | ||
43521d06 | 1574 | error = git_reference_foreach(repo, list_flags, &cb__reflist_add, (void *)&ref_list); |
7ad96e51 VM |
1575 | |
1576 | if (error < GIT_SUCCESS) { | |
09e8de0f | 1577 | git_vector_free(&ref_list); |
0d5d5190 | 1578 | return error; |
7ad96e51 VM |
1579 | } |
1580 | ||
09e8de0f VM |
1581 | array->strings = (char **)ref_list.contents; |
1582 | array->count = ref_list.length; | |
00571828 VM |
1583 | return GIT_SUCCESS; |
1584 | } | |
87d3acf4 VM |
1585 | |
1586 | ||
1587 | ||
1588 | ||
1589 | /***************************************** | |
1590 | * Init/free (repository API) | |
1591 | *****************************************/ | |
2f8a8ab2 VM |
1592 | int git_repository__refcache_init(git_refcache *refs) |
1593 | { | |
1594 | assert(refs); | |
9282e921 | 1595 | |
87d3acf4 | 1596 | refs->loose_cache = git_hashtable_alloc( |
86194b24 VM |
1597 | default_table_size, |
1598 | reftable_hash, | |
1599 | (git_hash_keyeq_ptr)strcmp); | |
1600 | ||
87d3acf4 VM |
1601 | /* packfile loaded lazily */ |
1602 | refs->packfile = NULL; | |
9282e921 | 1603 | |
87d3acf4 | 1604 | return (refs->loose_cache) ? GIT_SUCCESS : GIT_ENOMEM; |
2f8a8ab2 | 1605 | } |
9282e921 | 1606 | |
2f8a8ab2 VM |
1607 | void git_repository__refcache_free(git_refcache *refs) |
1608 | { | |
2f8a8ab2 | 1609 | git_reference *reference; |
402a47a7 | 1610 | const void *GIT_UNUSED(_unused); |
9282e921 | 1611 | |
2f8a8ab2 VM |
1612 | assert(refs); |
1613 | ||
87d3acf4 | 1614 | GIT_HASHTABLE_FOREACH(refs->loose_cache, _unused, reference, |
86194b24 VM |
1615 | reference_free(reference); |
1616 | ); | |
1617 | ||
87d3acf4 | 1618 | git_hashtable_free(refs->loose_cache); |
2f8a8ab2 | 1619 | |
87d3acf4 VM |
1620 | if (refs->packfile) { |
1621 | GIT_HASHTABLE_FOREACH(refs->packfile, _unused, reference, | |
1622 | reference_free(reference); | |
1623 | ); | |
1624 | ||
1625 | git_hashtable_free(refs->packfile); | |
1626 | } | |
9282e921 | 1627 | } |
2f8a8ab2 | 1628 | |
87d3acf4 VM |
1629 | |
1630 | ||
1631 | /***************************************** | |
1632 | * Name normalization | |
1633 | *****************************************/ | |
aa2120e9 | 1634 | static int check_valid_ref_char(char ch) |
1635 | { | |
1636 | if (ch <= ' ') | |
0d5d5190 | 1637 | return GIT_ERROR; |
aa2120e9 | 1638 | |
1639 | switch (ch) { | |
1640 | case '~': | |
1641 | case '^': | |
1642 | case ':': | |
1643 | case '\\': | |
1644 | case '?': | |
1645 | case '[': | |
e1be1028 | 1646 | case '*': |
0d5d5190 | 1647 | return GIT_ERROR; |
aa2120e9 | 1648 | break; |
1649 | ||
1650 | default: | |
1651 | return GIT_SUCCESS; | |
1652 | } | |
1653 | } | |
1654 | ||
3101a3e5 | 1655 | static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref) |
aa2120e9 | 1656 | { |
aa2120e9 | 1657 | const char *name_end, *buffer_out_start; |
1658 | char *current; | |
1659 | int contains_a_slash = 0; | |
1660 | ||
1661 | assert(name && buffer_out); | |
1662 | ||
1663 | buffer_out_start = buffer_out; | |
1664 | current = (char *)name; | |
1665 | name_end = name + strlen(name); | |
1666 | ||
3101a3e5 VM |
1667 | /* Terminating null byte */ |
1668 | out_size--; | |
1669 | ||
aa2120e9 | 1670 | /* A refname can not be empty */ |
1671 | if (name_end == name) | |
5bdf7b9f | 1672 | return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name is empty"); |
aa2120e9 | 1673 | |
1674 | /* A refname can not end with a dot or a slash */ | |
1675 | if (*(name_end - 1) == '.' || *(name_end - 1) == '/') | |
5bdf7b9f | 1676 | return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with dot or slash"); |
aa2120e9 | 1677 | |
3101a3e5 | 1678 | while (current < name_end && out_size) { |
aa2120e9 | 1679 | if (check_valid_ref_char(*current)) |
0d5d5190 | 1680 | return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains invalid characters"); |
aa2120e9 | 1681 | |
1682 | if (buffer_out > buffer_out_start) { | |
1683 | char prev = *(buffer_out - 1); | |
1684 | ||
1685 | /* A refname can not start with a dot nor contain a double dot */ | |
1686 | if (*current == '.' && ((prev == '.') || (prev == '/'))) | |
5bdf7b9f | 1687 | return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name starts with a dot or contains a double dot"); |
aa2120e9 | 1688 | |
1689 | /* '@{' is forbidden within a refname */ | |
1690 | if (*current == '{' && prev == '@') | |
5bdf7b9f | 1691 | return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains '@{'"); |
aa2120e9 | 1692 | |
1693 | /* Prevent multiple slashes from being added to the output */ | |
1694 | if (*current == '/' && prev == '/') { | |
1695 | current++; | |
1696 | continue; | |
1697 | } | |
1698 | } | |
1699 | ||
e1be1028 | 1700 | if (*current == '/') |
aa2120e9 | 1701 | contains_a_slash = 1; |
aa2120e9 | 1702 | |
1703 | *buffer_out++ = *current++; | |
3101a3e5 | 1704 | out_size--; |
aa2120e9 | 1705 | } |
1706 | ||
3101a3e5 VM |
1707 | if (!out_size) |
1708 | return git__throw(GIT_EINVALIDREFNAME, "Reference name is too long"); | |
1709 | ||
83c95128 | 1710 | /* Object id refname have to contain at least one slash, except |
df30eac1 JP |
1711 | * for HEAD in a detached state or MERGE_HEAD if we're in the |
1712 | * middle of a merge */ | |
1713 | if (is_oid_ref && !contains_a_slash && (strcmp(name, GIT_HEAD_FILE) && strcmp(name, GIT_MERGE_HEAD_FILE))) | |
0d5d5190 | 1714 | return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains no slashes"); |
aa2120e9 | 1715 | |
1716 | /* A refname can not end with ".lock" */ | |
1717 | if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION)) | |
0d5d5190 | 1718 | return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with '.lock'"); |
aa2120e9 | 1719 | |
1720 | *buffer_out = '\0'; | |
1721 | ||
83c95128 CMN |
1722 | /* |
1723 | * For object id references, name has to start with refs/. Again, | |
1724 | * we need to allow HEAD to be in a detached state. | |
1725 | */ | |
0d5d5190 VM |
1726 | if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) || |
1727 | strcmp(buffer_out_start, GIT_HEAD_FILE))) | |
5bdf7b9f | 1728 | return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name does not start with 'refs/'"); |
aa2120e9 | 1729 | |
0d5d5190 | 1730 | return GIT_SUCCESS; |
aa2120e9 | 1731 | } |
2f8a8ab2 | 1732 | |
3101a3e5 | 1733 | int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name) |
86194b24 | 1734 | { |
3101a3e5 | 1735 | return normalize_name(buffer_out, out_size, name, 0); |
86194b24 VM |
1736 | } |
1737 | ||
3101a3e5 | 1738 | int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name) |
86194b24 | 1739 | { |
3101a3e5 | 1740 | return normalize_name(buffer_out, out_size, name, 1); |
86194b24 VM |
1741 | } |
1742 | ||
1743 |