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