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