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