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