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