]> git.proxmox.com Git - libgit2.git/blame - src/refs.c
alloc doesn't take a refdb; git_refdb_free nicely in the tests
[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>
22#include <git2/refdb_backend.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(
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
69git_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 91void 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
102struct reference_available_t {
103 const char *new_ref;
104 const char *old_ref;
105 int available;
106};
107
1b6d8163
MS
108static 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 130static 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 */
165static 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 213int git_reference_delete(git_reference *ref)
a46ec457 214{
d00d5464 215 return git_refdb_delete(ref->db, ref);
a46ec457
MS
216}
217
d4a0b124 218int 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 224int 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
238int 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 297git_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 303const char *git_reference_name(const git_reference *ref)
87d3acf4
VM
304{
305 assert(ref);
d4a0b124 306 return ref->name;
87d3acf4
VM
307}
308
2508cc66 309git_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 315const 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
325const 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 335const 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 345static 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 389int 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
414int 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
433int 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
448int 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
464int 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 536rollback:
d00d5464 537 git_refdb_write(ref->db, ref);
0ffcf78a 538
d00d5464
ET
539on_error:
540 git_reference_free(result);
64093ce5 541
d00d5464 542 return error;
0ffcf78a 543}
7376ad99 544
2508cc66 545int 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
560int 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 572static 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 577int 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 603static 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 622static 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 658static 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 678int 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 766cleanup:
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 778int 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
802cleanup:
803 git_buf_free(&buf);
804 return error;
aa2120e9 805}
2f8a8ab2 806
c030ada7 807int 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
820int 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
838static 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 */
879int 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
887int 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
905int 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 923int git_reference__is_branch(const char *ref_name)
924{
925 return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
926}
927
88bcd515 928int 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 934int git_reference__is_remote(const char *ref_name)
935{
936 return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0;
937}
938
1c947daa
VM
939int git_reference_is_remote(git_reference *ref)
940{
941 assert(ref);
c1b5e8c4 942 return git_reference__is_remote(ref->name);
1c947daa 943}
31665948 944
945static 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 953int 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
989cleanup:
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 998int 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 1010int 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}