]>
Commit | Line | Data |
---|---|---|
9282e921 | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
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" |
d00d5464 | 14 | #include "refdb.h" |
9282e921 | 15 | |
87d3acf4 VM |
16 | #include <git2/tag.h> |
17 | #include <git2/object.h> | |
c07d9c95 | 18 | #include <git2/oid.h> |
4ba23be1 | 19 | #include <git2/branch.h> |
d00d5464 ET |
20 | #include <git2/refs.h> |
21 | #include <git2/refdb.h> | |
22 | #include <git2/refdb_backend.h> | |
87d3acf4 | 23 | |
c2b67043 | 24 | GIT__USE_STRMAP; |
01fed0a8 | 25 | |
f201d613 RB |
26 | #define DEFAULT_NESTING_LEVEL 5 |
27 | #define MAX_NESTING_LEVEL 10 | |
9282e921 | 28 | |
d4a0b124 VM |
29 | enum { |
30 | GIT_PACKREF_HAS_PEEL = 1, | |
31 | GIT_PACKREF_WAS_LOOSE = 2 | |
32 | }; | |
86194b24 | 33 | |
4e4eab52 | 34 | static git_reference *alloc_ref(const char *name) |
3be933b1 VM |
35 | { |
36 | git_reference *ref; | |
37 | size_t namelen = strlen(name); | |
86194b24 | 38 | |
3be933b1 VM |
39 | if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL) |
40 | return NULL; | |
41 | ||
3be933b1 VM |
42 | memcpy(ref->name, name, namelen + 1); |
43 | ||
44 | return ref; | |
45 | } | |
46 | ||
47 | git_reference *git_reference__alloc_symbolic( | |
d00d5464 | 48 | const char *name, |
3be933b1 | 49 | const char *target) |
2f8a8ab2 | 50 | { |
d00d5464 | 51 | git_reference *ref; |
2f8a8ab2 | 52 | |
4e4eab52 | 53 | assert(name && target); |
55e0f53d | 54 | |
4e4eab52 | 55 | ref = alloc_ref(name); |
3be933b1 | 56 | if (!ref) |
d00d5464 | 57 | return NULL; |
87d3acf4 | 58 | |
3be933b1 | 59 | ref->type = GIT_REF_SYMBOLIC; |
87d3acf4 | 60 | |
3be933b1 VM |
61 | if ((ref->target.symbolic = git__strdup(target)) == NULL) { |
62 | git__free(ref); | |
63 | return NULL; | |
d79f1da6 | 64 | } |
55e0f53d | 65 | |
3be933b1 VM |
66 | return ref; |
67 | } | |
68 | ||
69 | git_reference *git_reference__alloc( | |
3be933b1 VM |
70 | const char *name, |
71 | const git_oid *oid, | |
72 | const git_oid *peel) | |
73 | { | |
74 | git_reference *ref; | |
75 | ||
4e4eab52 | 76 | assert(name && oid); |
3be933b1 | 77 | |
4e4eab52 | 78 | ref = alloc_ref(name); |
3be933b1 VM |
79 | if (!ref) |
80 | return NULL; | |
81 | ||
82 | ref->type = GIT_REF_OID; | |
fedd0f9e | 83 | git_oid_cpy(&ref->target.oid, oid); |
3be933b1 VM |
84 | |
85 | if (peel != NULL) | |
fedd0f9e | 86 | git_oid_cpy(&ref->peel, peel); |
55e0f53d | 87 | |
d00d5464 | 88 | return ref; |
2f8a8ab2 | 89 | } |
9282e921 | 90 | |
d00d5464 | 91 | void git_reference_free(git_reference *reference) |
2f8a8ab2 | 92 | { |
d00d5464 ET |
93 | if (reference == NULL) |
94 | return; | |
2b397327 | 95 | |
3be933b1 | 96 | if (reference->type == GIT_REF_SYMBOLIC) |
d00d5464 | 97 | git__free(reference->target.symbolic); |
2f8a8ab2 | 98 | |
d00d5464 | 99 | git__free(reference); |
87d3acf4 | 100 | } |
2f8a8ab2 | 101 | |
45d387ac VM |
102 | struct reference_available_t { |
103 | const char *new_ref; | |
104 | const char *old_ref; | |
105 | int available; | |
106 | }; | |
107 | ||
1b6d8163 MS |
108 | static int _reference_available_cb(const char *ref, void *data) |
109 | { | |
45d387ac | 110 | struct reference_available_t *d; |
f120e92b | 111 | |
1b6d8163 | 112 | assert(ref && data); |
1a481123 | 113 | d = (struct reference_available_t *)data; |
1b6d8163 | 114 | |
45d387ac | 115 | if (!d->old_ref || strcmp(d->old_ref, ref)) { |
44ef8b1b RB |
116 | size_t reflen = strlen(ref); |
117 | size_t newlen = strlen(d->new_ref); | |
118 | size_t cmplen = reflen < newlen ? reflen : newlen; | |
45d387ac | 119 | const char *lead = reflen < newlen ? d->new_ref : ref; |
1b6d8163 | 120 | |
45d387ac VM |
121 | if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') { |
122 | d->available = 0; | |
123 | return -1; | |
124 | } | |
1b6d8163 MS |
125 | } |
126 | ||
45d387ac | 127 | return 0; |
1b6d8163 MS |
128 | } |
129 | ||
1a481123 | 130 | static int reference_path_available( |
d4a0b124 VM |
131 | git_repository *repo, |
132 | const char *ref, | |
1a481123 | 133 | const char* old_ref) |
1b6d8163 | 134 | { |
5dca2010 | 135 | int error; |
45d387ac | 136 | struct reference_available_t data; |
1b6d8163 | 137 | |
45d387ac VM |
138 | data.new_ref = ref; |
139 | data.old_ref = old_ref; | |
140 | data.available = 1; | |
1b6d8163 | 141 | |
5dca2010 RB |
142 | error = git_reference_foreach( |
143 | repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&data); | |
144 | if (error < 0) | |
145 | return error; | |
1a481123 VM |
146 | |
147 | if (!data.available) { | |
148 | giterr_set(GITERR_REFERENCE, | |
d00d5464 | 149 | "The path to reference '%s' collides with an existing one", ref); |
1a481123 | 150 | return -1; |
d4a0b124 | 151 | } |
2f8a8ab2 | 152 | |
1a481123 VM |
153 | return 0; |
154 | } | |
97769280 | 155 | |
1a481123 VM |
156 | /* |
157 | * Check if a reference could be written to disk, based on: | |
158 | * | |
159 | * - Whether a reference with the same name already exists, | |
160 | * and we are allowing or disallowing overwrites | |
161 | * | |
162 | * - Whether the name of the reference would collide with | |
163 | * an existing path | |
164 | */ | |
165 | static int reference_can_write( | |
166 | git_repository *repo, | |
167 | const char *refname, | |
168 | const char *previous_name, | |
169 | int force) | |
170 | { | |
d00d5464 ET |
171 | git_refdb *refdb; |
172 | ||
173 | if (git_repository_refdb__weakptr(&refdb, repo) < 0) | |
174 | return -1; | |
175 | ||
1a481123 VM |
176 | /* see if the reference shares a path with an existing reference; |
177 | * if a path is shared, we cannot create the reference, even when forcing */ | |
178 | if (reference_path_available(repo, refname, previous_name) < 0) | |
179 | return -1; | |
180 | ||
181 | /* check if the reference actually exists, but only if we are not forcing | |
182 | * the rename. If we are forcing, it's OK to overwrite */ | |
183 | if (!force) { | |
184 | int exists; | |
185 | ||
d00d5464 | 186 | if (git_refdb_exists(&exists, refdb, refname) < 0) |
1a481123 VM |
187 | return -1; |
188 | ||
189 | /* We cannot proceed if the reference already exists and we're not forcing | |
190 | * the rename; the existing one would be overwritten */ | |
191 | if (exists) { | |
192 | giterr_set(GITERR_REFERENCE, | |
e1de726c | 193 | "A reference with that name (%s) already exists", refname); |
904b67e6 | 194 | return GIT_EEXISTS; |
1a481123 VM |
195 | } |
196 | } | |
197 | ||
198 | /* FIXME: if the reference exists and we are forcing, do we really need to | |
199 | * remove the reference first? | |
200 | * | |
201 | * Two cases: | |
202 | * | |
203 | * - the reference already exists and is loose: not a problem, the file | |
204 | * gets overwritten on disk | |
205 | * | |
206 | * - the reference already exists and is packed: we write a new one as | |
207 | * loose, which by all means renders the packed one useless | |
208 | */ | |
209 | ||
210 | return 0; | |
d4a0b124 | 211 | } |
2f8a8ab2 | 212 | |
1a481123 | 213 | int git_reference_delete(git_reference *ref) |
a46ec457 | 214 | { |
d00d5464 | 215 | return git_refdb_delete(ref->db, ref); |
a46ec457 MS |
216 | } |
217 | ||
d4a0b124 | 218 | int git_reference_lookup(git_reference **ref_out, |
1a481123 | 219 | git_repository *repo, const char *name) |
a46ec457 | 220 | { |
f201d613 RB |
221 | return git_reference_lookup_resolved(ref_out, repo, name, 0); |
222 | } | |
223 | ||
2508cc66 | 224 | int git_reference_name_to_id( |
f201d613 RB |
225 | git_oid *out, git_repository *repo, const char *name) |
226 | { | |
227 | int error; | |
228 | git_reference *ref; | |
229 | ||
230 | if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0) | |
231 | return error; | |
232 | ||
2508cc66 | 233 | git_oid_cpy(out, git_reference_target(ref)); |
f201d613 RB |
234 | git_reference_free(ref); |
235 | return 0; | |
236 | } | |
237 | ||
238 | int git_reference_lookup_resolved( | |
239 | git_reference **ref_out, | |
240 | git_repository *repo, | |
241 | const char *name, | |
242 | int max_nesting) | |
243 | { | |
d00d5464 ET |
244 | char scan_name[GIT_REFNAME_MAX]; |
245 | git_ref_t scan_type; | |
246 | int error = 0, nesting; | |
247 | git_reference *ref = NULL; | |
248 | git_refdb *refdb; | |
d4a0b124 VM |
249 | |
250 | assert(ref_out && repo && name); | |
f201d613 | 251 | |
1a481123 | 252 | *ref_out = NULL; |
d4a0b124 | 253 | |
f201d613 RB |
254 | if (max_nesting > MAX_NESTING_LEVEL) |
255 | max_nesting = MAX_NESTING_LEVEL; | |
256 | else if (max_nesting < 0) | |
257 | max_nesting = DEFAULT_NESTING_LEVEL; | |
d00d5464 ET |
258 | |
259 | strncpy(scan_name, name, GIT_REFNAME_MAX); | |
260 | scan_type = GIT_REF_SYMBOLIC; | |
261 | ||
262 | if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) | |
263 | return -1; | |
a46ec457 | 264 | |
d00d5464 ET |
265 | if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0) |
266 | return error; | |
f201d613 RB |
267 | |
268 | for (nesting = max_nesting; | |
d00d5464 | 269 | nesting >= 0 && scan_type == GIT_REF_SYMBOLIC; |
f201d613 RB |
270 | nesting--) |
271 | { | |
d00d5464 ET |
272 | if (nesting != max_nesting) { |
273 | strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX); | |
274 | git_reference_free(ref); | |
275 | } | |
f201d613 | 276 | |
d00d5464 ET |
277 | if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0) |
278 | return error; | |
279 | ||
280 | scan_type = ref->type; | |
f201d613 RB |
281 | } |
282 | ||
d00d5464 | 283 | if (scan_type != GIT_REF_OID && max_nesting != 0) { |
f201d613 RB |
284 | giterr_set(GITERR_REFERENCE, |
285 | "Cannot resolve reference (>%u levels deep)", max_nesting); | |
d00d5464 | 286 | git_reference_free(ref); |
f201d613 RB |
287 | return -1; |
288 | } | |
289 | ||
d00d5464 | 290 | *ref_out = ref; |
f201d613 | 291 | return 0; |
a46ec457 MS |
292 | } |
293 | ||
d4a0b124 VM |
294 | /** |
295 | * Getters | |
296 | */ | |
2508cc66 | 297 | git_ref_t git_reference_type(const git_reference *ref) |
87d3acf4 VM |
298 | { |
299 | assert(ref); | |
d00d5464 | 300 | return ref->type; |
87d3acf4 VM |
301 | } |
302 | ||
2508cc66 | 303 | const char *git_reference_name(const git_reference *ref) |
87d3acf4 VM |
304 | { |
305 | assert(ref); | |
d4a0b124 | 306 | return ref->name; |
87d3acf4 VM |
307 | } |
308 | ||
2508cc66 | 309 | git_repository *git_reference_owner(const git_reference *ref) |
a46ec457 | 310 | { |
d4a0b124 | 311 | assert(ref); |
d00d5464 | 312 | return ref->db->repo; |
a46ec457 MS |
313 | } |
314 | ||
2508cc66 | 315 | const git_oid *git_reference_target(const git_reference *ref) |
87d3acf4 VM |
316 | { |
317 | assert(ref); | |
318 | ||
d00d5464 | 319 | if (ref->type != GIT_REF_OID) |
87d3acf4 VM |
320 | return NULL; |
321 | ||
fedd0f9e | 322 | return &ref->target.oid; |
3be933b1 VM |
323 | } |
324 | ||
325 | const git_oid *git_reference_target_peel(const git_reference *ref) | |
326 | { | |
327 | assert(ref); | |
328 | ||
fedd0f9e | 329 | if (ref->type != GIT_REF_OID || git_oid_iszero(&ref->peel)) |
3be933b1 VM |
330 | return NULL; |
331 | ||
fedd0f9e | 332 | return &ref->peel; |
87d3acf4 VM |
333 | } |
334 | ||
2508cc66 | 335 | const char *git_reference_symbolic_target(const git_reference *ref) |
a46ec457 | 336 | { |
d4a0b124 | 337 | assert(ref); |
a46ec457 | 338 | |
d00d5464 | 339 | if (ref->type != GIT_REF_SYMBOLIC) |
a46ec457 MS |
340 | return NULL; |
341 | ||
d4a0b124 | 342 | return ref->target.symbolic; |
a46ec457 MS |
343 | } |
344 | ||
d00d5464 | 345 | static int reference__create( |
d4a0b124 VM |
346 | git_reference **ref_out, |
347 | git_repository *repo, | |
348 | const char *name, | |
d00d5464 ET |
349 | const git_oid *oid, |
350 | const char *symbolic, | |
1a481123 | 351 | int force) |
d5afc039 VM |
352 | { |
353 | char normalized[GIT_REFNAME_MAX]; | |
d00d5464 | 354 | git_refdb *refdb; |
d4a0b124 | 355 | git_reference *ref = NULL; |
d00d5464 ET |
356 | int error = 0; |
357 | ||
358 | if (ref_out) | |
359 | *ref_out = NULL; | |
360 | ||
361 | if ((error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name)) < 0 || | |
362 | (error = reference_can_write(repo, normalized, NULL, force)) < 0 || | |
363 | (error = git_repository_refdb__weakptr(&refdb, repo)) < 0) | |
c1281493 | 364 | return error; |
3be933b1 VM |
365 | |
366 | if (oid != NULL) { | |
367 | assert(symbolic == NULL); | |
4e4eab52 | 368 | ref = git_reference__alloc(name, oid, NULL); |
3be933b1 | 369 | } else { |
4e4eab52 | 370 | ref = git_reference__alloc_symbolic(name, symbolic); |
3be933b1 | 371 | } |
d00d5464 | 372 | |
13421eee | 373 | GITERR_CHECK_ALLOC(ref); |
4e4eab52 | 374 | ref->db = refdb; |
d5afc039 | 375 | |
d00d5464 | 376 | if ((error = git_refdb_write(refdb, ref)) < 0) { |
45d387ac | 377 | git_reference_free(ref); |
d00d5464 | 378 | return error; |
45d387ac | 379 | } |
d00d5464 ET |
380 | |
381 | if (ref_out == NULL) | |
d4a0b124 | 382 | git_reference_free(ref); |
d00d5464 | 383 | else |
d4a0b124 | 384 | *ref_out = ref; |
d5afc039 | 385 | |
45d387ac | 386 | return 0; |
d5afc039 VM |
387 | } |
388 | ||
2508cc66 | 389 | int git_reference_create( |
d4a0b124 VM |
390 | git_reference **ref_out, |
391 | git_repository *repo, | |
392 | const char *name, | |
d00d5464 | 393 | const git_oid *oid, |
1a481123 | 394 | int force) |
a46ec457 | 395 | { |
d00d5464 ET |
396 | git_odb *odb; |
397 | int error = 0; | |
d5afc039 | 398 | |
d00d5464 ET |
399 | assert(repo && name && oid); |
400 | ||
401 | /* Sanity check the reference being created - target must exist. */ | |
402 | if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) | |
c1281493 | 403 | return error; |
d00d5464 ET |
404 | |
405 | if (!git_odb_exists(odb, oid)) { | |
406 | giterr_set(GITERR_REFERENCE, | |
407 | "Target OID for the reference doesn't exist on the repository"); | |
45d387ac VM |
408 | return -1; |
409 | } | |
d00d5464 ET |
410 | |
411 | return reference__create(ref_out, repo, name, oid, NULL, force); | |
a46ec457 | 412 | } |
87d3acf4 | 413 | |
d00d5464 ET |
414 | int git_reference_symbolic_create( |
415 | git_reference **ref_out, | |
416 | git_repository *repo, | |
417 | const char *name, | |
418 | const char *target, | |
419 | int force) | |
420 | { | |
421 | char normalized[GIT_REFNAME_MAX]; | |
422 | int error = 0; | |
87d3acf4 | 423 | |
d00d5464 ET |
424 | assert(repo && name && target); |
425 | ||
426 | if ((error = git_reference__normalize_name_lax( | |
427 | normalized, sizeof(normalized), target)) < 0) | |
428 | return error; | |
9a53df7e | 429 | |
d00d5464 ET |
430 | return reference__create(ref_out, repo, name, NULL, normalized, force); |
431 | } | |
9462c471 | 432 | |
d00d5464 ET |
433 | int git_reference_set_target( |
434 | git_reference **out, | |
435 | git_reference *ref, | |
436 | const git_oid *id) | |
437 | { | |
438 | assert(out && ref && id); | |
439 | ||
440 | if (ref->type != GIT_REF_OID) { | |
441 | giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference"); | |
45d387ac VM |
442 | return -1; |
443 | } | |
87d3acf4 | 444 | |
d00d5464 | 445 | return git_reference_create(out, ref->db->repo, ref->name, id, 1); |
a46ec457 MS |
446 | } |
447 | ||
d00d5464 ET |
448 | int git_reference_symbolic_set_target( |
449 | git_reference **out, | |
450 | git_reference *ref, | |
451 | const char *target) | |
87d3acf4 | 452 | { |
d00d5464 ET |
453 | assert(out && ref && target); |
454 | ||
455 | if (ref->type != GIT_REF_SYMBOLIC) { | |
1a481123 | 456 | giterr_set(GITERR_REFERENCE, |
45d387ac VM |
457 | "Cannot set symbolic target on a direct reference"); |
458 | return -1; | |
459 | } | |
d00d5464 ET |
460 | |
461 | return git_reference_symbolic_create(out, ref->db->repo, ref->name, target, 1); | |
87d3acf4 VM |
462 | } |
463 | ||
d00d5464 ET |
464 | int git_reference_rename( |
465 | git_reference **out, | |
466 | git_reference *ref, | |
467 | const char *new_name, | |
468 | int force) | |
7376ad99 | 469 | { |
2e0c8816 | 470 | unsigned int normalization_flags; |
0ffcf78a | 471 | char normalized[GIT_REFNAME_MAX]; |
f3cc7834 | 472 | bool should_head_be_updated = false; |
d00d5464 | 473 | git_reference *result = NULL; |
d00d5464 | 474 | int error = 0; |
10c06114 | 475 | int reference_has_log; |
d00d5464 ET |
476 | |
477 | *out = NULL; | |
478 | ||
479 | normalization_flags = ref->type == GIT_REF_SYMBOLIC ? | |
480 | GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL; | |
481 | ||
3be933b1 VM |
482 | if ((error = git_reference_normalize_name( |
483 | normalized, sizeof(normalized), new_name, normalization_flags)) < 0 || | |
d00d5464 ET |
484 | (error = reference_can_write(ref->db->repo, normalized, ref->name, force)) < 0) |
485 | return error; | |
7376ad99 | 486 | |
0ffcf78a | 487 | /* |
d00d5464 | 488 | * Create the new reference. |
0ffcf78a | 489 | */ |
d00d5464 | 490 | if (ref->type == GIT_REF_OID) { |
4e4eab52 | 491 | result = git_reference__alloc(new_name, &ref->target.oid, &ref->peel); |
3be933b1 | 492 | } else if (ref->type == GIT_REF_SYMBOLIC) { |
4e4eab52 | 493 | result = git_reference__alloc_symbolic(new_name, ref->target.symbolic); |
0ffcf78a | 494 | } else { |
3be933b1 | 495 | assert(0); |
0ffcf78a | 496 | } |
3be933b1 VM |
497 | |
498 | if (result == NULL) | |
d00d5464 | 499 | return -1; |
7376ad99 | 500 | |
4e4eab52 ET |
501 | result->db = ref->db; |
502 | ||
d00d5464 | 503 | /* Check if we have to update HEAD. */ |
10c06114 | 504 | if ((error = git_branch_is_head(ref)) < 0) |
d00d5464 ET |
505 | goto on_error; |
506 | ||
10c06114 AS |
507 | should_head_be_updated = (error > 0); |
508 | ||
d00d5464 | 509 | /* Now delete the old ref and save the new one. */ |
10c06114 | 510 | if ((error = git_refdb_delete(ref->db, ref)) < 0) |
d00d5464 ET |
511 | goto on_error; |
512 | ||
513 | /* Save the new reference. */ | |
514 | if ((error = git_refdb_write(ref->db, result)) < 0) | |
64093ce5 | 515 | goto rollback; |
d00d5464 ET |
516 | |
517 | /* Update HEAD it was poiting to the reference being renamed. */ | |
10c06114 | 518 | if (should_head_be_updated && (error = git_repository_set_head(ref->db->repo, new_name)) < 0) { |
d00d5464 ET |
519 | giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference"); |
520 | goto on_error; | |
d4a0b124 VM |
521 | } |
522 | ||
d00d5464 | 523 | /* Rename the reflog file, if it exists. */ |
10c06114 AS |
524 | reference_has_log = git_reference_has_log(ref); |
525 | if (reference_has_log < 0) { | |
526 | error = reference_has_log; | |
527 | goto on_error; | |
528 | } | |
529 | if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0) | |
d00d5464 | 530 | goto on_error; |
7376ad99 | 531 | |
d00d5464 | 532 | *out = result; |
45d387ac | 533 | |
d00d5464 | 534 | return error; |
7376ad99 | 535 | |
0ffcf78a | 536 | rollback: |
d00d5464 | 537 | git_refdb_write(ref->db, ref); |
0ffcf78a | 538 | |
d00d5464 ET |
539 | on_error: |
540 | git_reference_free(result); | |
64093ce5 | 541 | |
d00d5464 | 542 | return error; |
0ffcf78a | 543 | } |
7376ad99 | 544 | |
2508cc66 | 545 | int git_reference_resolve(git_reference **ref_out, const git_reference *ref) |
87d3acf4 | 546 | { |
3be933b1 VM |
547 | switch (git_reference_type(ref)) { |
548 | case GIT_REF_OID: | |
d00d5464 | 549 | return git_reference_lookup(ref_out, ref->db->repo, ref->name); |
3be933b1 VM |
550 | |
551 | case GIT_REF_SYMBOLIC: | |
552 | return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1); | |
553 | ||
554 | default: | |
555 | giterr_set(GITERR_REFERENCE, "Invalid reference"); | |
556 | return -1; | |
557 | } | |
87d3acf4 VM |
558 | } |
559 | ||
d4a0b124 VM |
560 | int git_reference_foreach( |
561 | git_repository *repo, | |
562 | unsigned int list_flags, | |
eecc8050 | 563 | git_reference_foreach_cb callback, |
1a481123 | 564 | void *payload) |
00571828 | 565 | { |
d00d5464 ET |
566 | git_refdb *refdb; |
567 | git_repository_refdb__weakptr(&refdb, repo); | |
00571828 | 568 | |
d00d5464 | 569 | return git_refdb_foreach(refdb, list_flags, callback, payload); |
09e8de0f VM |
570 | } |
571 | ||
d568d585 | 572 | static int cb__reflist_add(const char *ref, void *data) |
09e8de0f VM |
573 | { |
574 | return git_vector_insert((git_vector *)data, git__strdup(ref)); | |
575 | } | |
576 | ||
4fbd1c00 | 577 | int git_reference_list( |
d4a0b124 VM |
578 | git_strarray *array, |
579 | git_repository *repo, | |
1a481123 | 580 | unsigned int list_flags) |
09e8de0f | 581 | { |
09e8de0f VM |
582 | git_vector ref_list; |
583 | ||
584 | assert(array && repo); | |
585 | ||
586 | array->strings = NULL; | |
587 | array->count = 0; | |
588 | ||
0d0fa7c3 | 589 | if (git_vector_init(&ref_list, 8, NULL) < 0) |
45d387ac | 590 | return -1; |
7ad96e51 | 591 | |
45d387ac | 592 | if (git_reference_foreach( |
1a481123 | 593 | repo, list_flags, &cb__reflist_add, (void *)&ref_list) < 0) { |
09e8de0f | 594 | git_vector_free(&ref_list); |
45d387ac | 595 | return -1; |
7ad96e51 VM |
596 | } |
597 | ||
09e8de0f VM |
598 | array->strings = (char **)ref_list.contents; |
599 | array->count = ref_list.length; | |
1a481123 | 600 | return 0; |
00571828 | 601 | } |
87d3acf4 | 602 | |
d4a0b124 | 603 | static int is_valid_ref_char(char ch) |
aa2120e9 | 604 | { |
50a8fd03 | 605 | if ((unsigned) ch <= ' ') |
d4a0b124 | 606 | return 0; |
aa2120e9 | 607 | |
608 | switch (ch) { | |
609 | case '~': | |
610 | case '^': | |
611 | case ':': | |
612 | case '\\': | |
613 | case '?': | |
614 | case '[': | |
e1be1028 | 615 | case '*': |
d4a0b124 | 616 | return 0; |
aa2120e9 | 617 | default: |
d4a0b124 | 618 | return 1; |
aa2120e9 | 619 | } |
620 | } | |
621 | ||
c030ada7 | 622 | static int ensure_segment_validity(const char *name) |
aa2120e9 | 623 | { |
c030ada7 | 624 | const char *current = name; |
625 | char prev = '\0'; | |
0d1b094b RB |
626 | const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION); |
627 | int segment_len; | |
aa2120e9 | 628 | |
c030ada7 | 629 | if (*current == '.') |
630 | return -1; /* Refname starts with "." */ | |
aa2120e9 | 631 | |
c030ada7 | 632 | for (current = name; ; current++) { |
633 | if (*current == '\0' || *current == '/') | |
634 | break; | |
2e0c8816 | 635 | |
c030ada7 | 636 | if (!is_valid_ref_char(*current)) |
637 | return -1; /* Illegal character in refname */ | |
aa2120e9 | 638 | |
c030ada7 | 639 | if (prev == '.' && *current == '.') |
640 | return -1; /* Refname contains ".." */ | |
3101a3e5 | 641 | |
c030ada7 | 642 | if (prev == '@' && *current == '{') |
643 | return -1; /* Refname contains "@{" */ | |
aa2120e9 | 644 | |
c030ada7 | 645 | prev = *current; |
646 | } | |
aa2120e9 | 647 | |
0d1b094b RB |
648 | segment_len = (int)(current - name); |
649 | ||
4d811c3b | 650 | /* A refname component can not end with ".lock" */ |
0d1b094b | 651 | if (segment_len >= lock_len && |
2bca5b67 | 652 | !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len)) |
4d811c3b | 653 | return -1; |
654 | ||
0d1b094b | 655 | return segment_len; |
c030ada7 | 656 | } |
aa2120e9 | 657 | |
7c411fd9 | 658 | static bool is_all_caps_and_underscore(const char *name, size_t len) |
77e06d7e | 659 | { |
7c411fd9 | 660 | size_t i; |
77e06d7e | 661 | char c; |
662 | ||
663 | assert(name && len > 0); | |
664 | ||
665 | for (i = 0; i < len; i++) | |
666 | { | |
667 | c = name[i]; | |
668 | if ((c < 'A' || c > 'Z') && c != '_') | |
669 | return false; | |
670 | } | |
671 | ||
672 | if (*name == '_' || name[len - 1] == '_') | |
673 | return false; | |
674 | ||
675 | return true; | |
676 | } | |
677 | ||
c030ada7 | 678 | int git_reference__normalize_name( |
679 | git_buf *buf, | |
680 | const char *name, | |
681 | unsigned int flags) | |
682 | { | |
683 | // Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 | |
684 | ||
685 | char *current; | |
80d9d1df | 686 | int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC; |
77e06d7e | 687 | unsigned int process_flags; |
688 | bool normalize = (buf != NULL); | |
689 | assert(name); | |
c030ada7 | 690 | |
77e06d7e | 691 | process_flags = flags; |
c030ada7 | 692 | current = (char *)name; |
693 | ||
bb45c57f CMN |
694 | if (*current == '/') |
695 | goto cleanup; | |
696 | ||
77e06d7e | 697 | if (normalize) |
698 | git_buf_clear(buf); | |
c030ada7 | 699 | |
700 | while (true) { | |
701 | segment_len = ensure_segment_validity(current); | |
702 | if (segment_len < 0) { | |
77e06d7e | 703 | if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && |
c030ada7 | 704 | current[0] == '*' && |
705 | (current[1] == '\0' || current[1] == '/')) { | |
706 | /* Accept one wildcard as a full refname component. */ | |
77e06d7e | 707 | process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN; |
c030ada7 | 708 | segment_len = 1; |
709 | } else | |
710 | goto cleanup; | |
711 | } | |
aa2120e9 | 712 | |
c030ada7 | 713 | if (segment_len > 0) { |
77e06d7e | 714 | if (normalize) { |
7c411fd9 | 715 | size_t cur_len = git_buf_len(buf); |
aa2120e9 | 716 | |
77e06d7e | 717 | git_buf_joinpath(buf, git_buf_cstr(buf), current); |
7c411fd9 | 718 | git_buf_truncate(buf, |
77e06d7e | 719 | cur_len + segment_len + (segments_count ? 1 : 0)); |
aa2120e9 | 720 | |
80d9d1df | 721 | if (git_buf_oom(buf)) { |
722 | error = -1; | |
77e06d7e | 723 | goto cleanup; |
80d9d1df | 724 | } |
77e06d7e | 725 | } |
aa2120e9 | 726 | |
77e06d7e | 727 | segments_count++; |
0844ed06 | 728 | } |
2e0c8816 | 729 | |
2bca5b67 | 730 | /* No empty segment is allowed when not normalizing */ |
731 | if (segment_len == 0 && !normalize) | |
e5ef0f18 | 732 | goto cleanup; |
0d1b094b | 733 | |
c030ada7 | 734 | if (current[segment_len] == '\0') |
735 | break; | |
aa2120e9 | 736 | |
c030ada7 | 737 | current += segment_len + 1; |
2e0c8816 | 738 | } |
3101a3e5 | 739 | |
c030ada7 | 740 | /* A refname can not be empty */ |
77e06d7e | 741 | if (segment_len == 0 && segments_count == 0) |
c030ada7 | 742 | goto cleanup; |
743 | ||
744 | /* A refname can not end with "." */ | |
745 | if (current[segment_len - 1] == '.') | |
746 | goto cleanup; | |
747 | ||
748 | /* A refname can not end with "/" */ | |
749 | if (current[segment_len - 1] == '/') | |
750 | goto cleanup; | |
751 | ||
77e06d7e | 752 | if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL)) |
753 | goto cleanup; | |
754 | ||
755 | if ((segments_count == 1 ) && | |
7c411fd9 | 756 | !(is_all_caps_and_underscore(name, (size_t)segment_len) || |
77e06d7e | 757 | ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name)))) |
758 | goto cleanup; | |
759 | ||
760 | if ((segments_count > 1) | |
761 | && (is_all_caps_and_underscore(name, strchr(name, '/') - name))) | |
762 | goto cleanup; | |
aa2120e9 | 763 | |
c030ada7 | 764 | error = 0; |
aa2120e9 | 765 | |
c030ada7 | 766 | cleanup: |
80d9d1df | 767 | if (error == GIT_EINVALIDSPEC) |
c030ada7 | 768 | giterr_set( |
769 | GITERR_REFERENCE, | |
770 | "The given reference name '%s' is not valid", name); | |
aa2120e9 | 771 | |
3865f7f6 RB |
772 | if (error && normalize) |
773 | git_buf_free(buf); | |
774 | ||
c030ada7 | 775 | return error; |
776 | } | |
1a481123 | 777 | |
c030ada7 | 778 | int git_reference_normalize_name( |
779 | char *buffer_out, | |
780 | size_t buffer_size, | |
781 | const char *name, | |
782 | unsigned int flags) | |
783 | { | |
784 | git_buf buf = GIT_BUF_INIT; | |
785 | int error; | |
786 | ||
787 | if ((error = git_reference__normalize_name(&buf, name, flags)) < 0) | |
788 | goto cleanup; | |
789 | ||
790 | if (git_buf_len(&buf) > buffer_size - 1) { | |
791 | giterr_set( | |
2e0c8816 | 792 | GITERR_REFERENCE, |
c030ada7 | 793 | "The provided buffer is too short to hold the normalization of '%s'", name); |
794 | error = GIT_EBUFS; | |
795 | goto cleanup; | |
796 | } | |
797 | ||
798 | git_buf_copy_cstr(buffer_out, buffer_size, &buf); | |
799 | ||
800 | error = 0; | |
801 | ||
802 | cleanup: | |
803 | git_buf_free(&buf); | |
804 | return error; | |
aa2120e9 | 805 | } |
2f8a8ab2 | 806 | |
c030ada7 | 807 | int git_reference__normalize_name_lax( |
d4a0b124 VM |
808 | char *buffer_out, |
809 | size_t out_size, | |
810 | const char *name) | |
86194b24 | 811 | { |
2e0c8816 | 812 | return git_reference_normalize_name( |
813 | buffer_out, | |
814 | out_size, | |
815 | name, | |
816 | GIT_REF_FORMAT_ALLOW_ONELEVEL); | |
86194b24 | 817 | } |
f201d613 RB |
818 | #define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC) |
819 | ||
820 | int git_reference_cmp(git_reference *ref1, git_reference *ref2) | |
821 | { | |
3be933b1 | 822 | git_ref_t type1, type2; |
f201d613 RB |
823 | assert(ref1 && ref2); |
824 | ||
3be933b1 VM |
825 | type1 = git_reference_type(ref1); |
826 | type2 = git_reference_type(ref2); | |
827 | ||
f201d613 | 828 | /* let's put symbolic refs before OIDs */ |
3be933b1 VM |
829 | if (type1 != type2) |
830 | return (type1 == GIT_REF_SYMBOLIC) ? -1 : 1; | |
f201d613 | 831 | |
3be933b1 | 832 | if (type1 == GIT_REF_SYMBOLIC) |
f201d613 RB |
833 | return strcmp(ref1->target.symbolic, ref2->target.symbolic); |
834 | ||
fedd0f9e | 835 | return git_oid_cmp(&ref1->target.oid, &ref2->target.oid); |
f201d613 RB |
836 | } |
837 | ||
d00d5464 ET |
838 | static int reference__update_terminal( |
839 | git_repository *repo, | |
840 | const char *ref_name, | |
841 | const git_oid *oid, | |
842 | int nesting) | |
edebceff | 843 | { |
844 | git_reference *ref; | |
d00d5464 | 845 | int error = 0; |
edebceff | 846 | |
d00d5464 ET |
847 | if (nesting > MAX_NESTING_LEVEL) |
848 | return GIT_ENOTFOUND; | |
849 | ||
850 | error = git_reference_lookup(&ref, repo, ref_name); | |
edebceff | 851 | |
d00d5464 ET |
852 | /* If we haven't found the reference at all, create a new reference. */ |
853 | if (error == GIT_ENOTFOUND) { | |
edebceff | 854 | giterr_clear(); |
d00d5464 | 855 | return git_reference_create(NULL, repo, ref_name, oid, 0); |
edebceff | 856 | } |
d00d5464 ET |
857 | |
858 | if (error < 0) | |
859 | return error; | |
860 | ||
861 | /* If the ref is a symbolic reference, follow its target. */ | |
edebceff | 862 | if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { |
d00d5464 ET |
863 | error = reference__update_terminal(repo, git_reference_symbolic_target(ref), oid, |
864 | nesting+1); | |
edebceff | 865 | git_reference_free(ref); |
d00d5464 ET |
866 | } else { |
867 | git_reference_free(ref); | |
868 | error = git_reference_create(NULL, repo, ref_name, oid, 1); | |
edebceff | 869 | } |
d00d5464 ET |
870 | |
871 | return error; | |
edebceff | 872 | } |
527ed554 | 873 | |
d00d5464 ET |
874 | /* |
875 | * Starting with the reference given by `ref_name`, follows symbolic | |
876 | * references until a direct reference is found and updated the OID | |
877 | * on that direct reference to `oid`. | |
878 | */ | |
879 | int git_reference__update_terminal( | |
880 | git_repository *repo, | |
881 | const char *ref_name, | |
882 | const git_oid *oid) | |
527ed554 | 883 | { |
d00d5464 | 884 | return reference__update_terminal(repo, ref_name, oid, 0); |
527ed554 | 885 | } |
886 | ||
887 | int git_reference_foreach_glob( | |
888 | git_repository *repo, | |
889 | const char *glob, | |
890 | unsigned int list_flags, | |
891 | int (*callback)( | |
892 | const char *reference_name, | |
893 | void *payload), | |
894 | void *payload) | |
895 | { | |
d00d5464 | 896 | git_refdb *refdb; |
527ed554 | 897 | |
898 | assert(repo && glob && callback); | |
899 | ||
d00d5464 | 900 | git_repository_refdb__weakptr(&refdb, repo); |
527ed554 | 901 | |
d00d5464 | 902 | return git_refdb_foreach_glob(refdb, glob, list_flags, callback, payload); |
527ed554 | 903 | } |
75261421 | 904 | |
905 | int git_reference_has_log( | |
906 | git_reference *ref) | |
907 | { | |
908 | git_buf path = GIT_BUF_INIT; | |
909 | int result; | |
910 | ||
911 | assert(ref); | |
912 | ||
d00d5464 ET |
913 | if (git_buf_join_n(&path, '/', 3, ref->db->repo->path_repository, |
914 | GIT_REFLOG_DIR, ref->name) < 0) | |
75261421 | 915 | return -1; |
916 | ||
917 | result = git_path_isfile(git_buf_cstr(&path)); | |
918 | git_buf_free(&path); | |
919 | ||
920 | return result; | |
921 | } | |
84f18e35 | 922 | |
bf031581 | 923 | int git_reference__is_branch(const char *ref_name) |
924 | { | |
925 | return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0; | |
926 | } | |
927 | ||
88bcd515 | 928 | int git_reference_is_branch(git_reference *ref) |
929 | { | |
930 | assert(ref); | |
bf031581 | 931 | return git_reference__is_branch(ref->name); |
88bcd515 | 932 | } |
1c947daa | 933 | |
c1b5e8c4 | 934 | int git_reference__is_remote(const char *ref_name) |
935 | { | |
936 | return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0; | |
937 | } | |
938 | ||
1c947daa VM |
939 | int git_reference_is_remote(git_reference *ref) |
940 | { | |
941 | assert(ref); | |
c1b5e8c4 | 942 | return git_reference__is_remote(ref->name); |
1c947daa | 943 | } |
31665948 | 944 | |
945 | static int peel_error(int error, git_reference *ref, const char* msg) | |
946 | { | |
947 | giterr_set( | |
948 | GITERR_INVALID, | |
949 | "The reference '%s' cannot be peeled - %s", git_reference_name(ref), msg); | |
950 | return error; | |
951 | } | |
952 | ||
31665948 | 953 | int git_reference_peel( |
954 | git_object **peeled, | |
955 | git_reference *ref, | |
956 | git_otype target_type) | |
957 | { | |
958 | git_reference *resolved = NULL; | |
959 | git_object *target = NULL; | |
960 | int error; | |
961 | ||
962 | assert(ref); | |
963 | ||
3be933b1 VM |
964 | if (ref->type == GIT_REF_OID) { |
965 | resolved = ref; | |
966 | } else { | |
967 | if ((error = git_reference_resolve(&resolved, ref)) < 0) | |
968 | return peel_error(error, ref, "Cannot resolve reference"); | |
969 | } | |
970 | ||
fedd0f9e | 971 | if (!git_oid_iszero(&resolved->peel)) { |
3be933b1 | 972 | error = git_object_lookup(&target, |
fedd0f9e | 973 | git_reference_owner(ref), &resolved->peel, GIT_OBJ_ANY); |
3be933b1 VM |
974 | } else { |
975 | error = git_object_lookup(&target, | |
fedd0f9e | 976 | git_reference_owner(ref), &resolved->target.oid, GIT_OBJ_ANY); |
3be933b1 | 977 | } |
31665948 | 978 | |
3be933b1 | 979 | if (error < 0) { |
31665948 | 980 | peel_error(error, ref, "Cannot retrieve reference target"); |
981 | goto cleanup; | |
982 | } | |
b90500f0 | 983 | |
31665948 | 984 | if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG) |
575a54db | 985 | error = git_object_dup(peeled, target); |
b90500f0 | 986 | else |
31665948 | 987 | error = git_object_peel(peeled, target, target_type); |
988 | ||
989 | cleanup: | |
990 | git_object_free(target); | |
3be933b1 VM |
991 | |
992 | if (resolved != ref) | |
993 | git_reference_free(resolved); | |
994 | ||
31665948 | 995 | return error; |
996 | } | |
77e06d7e | 997 | |
0adfa20a | 998 | int git_reference__is_valid_name( |
999 | const char *refname, | |
1000 | unsigned int flags) | |
1001 | { | |
83458bb7 | 1002 | int error; |
1003 | ||
1004 | error = git_reference__normalize_name(NULL, refname, flags) == 0; | |
0adfa20a | 1005 | giterr_clear(); |
83458bb7 | 1006 | |
1007 | return error; | |
0adfa20a | 1008 | } |
1009 | ||
77e06d7e | 1010 | int git_reference_is_valid_name( |
1011 | const char *refname) | |
1012 | { | |
0adfa20a | 1013 | return git_reference__is_valid_name( |
77e06d7e | 1014 | refname, |
0adfa20a | 1015 | GIT_REF_FORMAT_ALLOW_ONELEVEL); |
77e06d7e | 1016 | } |