1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
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.
6 */
7
8 #include "refs.h"
9
10 #include "hash.h"
11 #include "repository.h"
12 #include "futils.h"
13 #include "filebuf.h"
14 #include "pack.h"
15 #include "reflog.h"
16 #include "refdb.h"
17
18 #include <git2/tag.h>
19 #include <git2/object.h>
20 #include <git2/oid.h>
21 #include <git2/branch.h>
22 #include <git2/refs.h>
23 #include <git2/refdb.h>
24 #include <git2/sys/refs.h>
25 #include <git2/signature.h>
26 #include <git2/commit.h>
27
28 bool git_reference__enable_symbolic_ref_target_validation = true;
29
30 enum {
31 GIT_PACKREF_HAS_PEEL = 1,
32 GIT_PACKREF_WAS_LOOSE = 2
33 };
34
alloc_ref(const char * name)35 static git_reference *alloc_ref(const char *name)
36 {
37 git_reference *ref = NULL;
38 size_t namelen = strlen(name), reflen;
39
40 if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) &&
41 !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) &&
42 (ref = git__calloc(1, reflen)) != NULL)
43 memcpy(ref->name, name, namelen + 1);
44
45 return ref;
46 }
47
git_reference__alloc_symbolic(const char * name,const char * target)48 git_reference *git_reference__alloc_symbolic(
49 const char *name, const char *target)
50 {
51 git_reference *ref;
52
53 assert(name && target);
54
55 ref = alloc_ref(name);
56 if (!ref)
57 return NULL;
58
59 ref->type = GIT_REFERENCE_SYMBOLIC;
60
61 if ((ref->target.symbolic = git__strdup(target)) == NULL) {
62 git__free(ref);
63 return NULL;
64 }
65
66 return ref;
67 }
68
git_reference__alloc(const char * name,const git_oid * oid,const git_oid * peel)69 git_reference *git_reference__alloc(
70 const char *name,
71 const git_oid *oid,
72 const git_oid *peel)
73 {
74 git_reference *ref;
75
76 assert(name && oid);
77
78 ref = alloc_ref(name);
79 if (!ref)
80 return NULL;
81
82 ref->type = GIT_REFERENCE_DIRECT;
83 git_oid_cpy(&ref->target.oid, oid);
84
85 if (peel != NULL)
86 git_oid_cpy(&ref->peel, peel);
87
88 return ref;
89 }
90
git_reference__realloc(git_reference ** ptr_to_ref,const char * name)91 git_reference *git_reference__realloc(
92 git_reference **ptr_to_ref, const char *name)
93 {
94 size_t namelen, reflen;
95 git_reference *rewrite = NULL;
96
97 assert(ptr_to_ref && name);
98
99 namelen = strlen(name);
100
101 if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) &&
102 !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) &&
103 (rewrite = git__realloc(*ptr_to_ref, reflen)) != NULL)
104 memcpy(rewrite->name, name, namelen + 1);
105
106 *ptr_to_ref = NULL;
107
108 return rewrite;
109 }
110
git_reference_dup(git_reference ** dest,git_reference * source)111 int git_reference_dup(git_reference **dest, git_reference *source)
112 {
113 if (source->type == GIT_REFERENCE_SYMBOLIC)
114 *dest = git_reference__alloc_symbolic(source->name, source->target.symbolic);
115 else
116 *dest = git_reference__alloc(source->name, &source->target.oid, &source->peel);
117
118 GIT_ERROR_CHECK_ALLOC(*dest);
119
120 (*dest)->db = source->db;
121 GIT_REFCOUNT_INC((*dest)->db);
122
123 return 0;
124 }
125
git_reference_free(git_reference * reference)126 void git_reference_free(git_reference *reference)
127 {
128 if (reference == NULL)
129 return;
130
131 if (reference->type == GIT_REFERENCE_SYMBOLIC)
132 git__free(reference->target.symbolic);
133
134 if (reference->db)
135 GIT_REFCOUNT_DEC(reference->db, git_refdb__free);
136
137 git__free(reference);
138 }
139
git_reference_delete(git_reference * ref)140 int git_reference_delete(git_reference *ref)
141 {
142 const git_oid *old_id = NULL;
143 const char *old_target = NULL;
144
145 if (!strcmp(ref->name, "HEAD")) {
146 git_error_set(GIT_ERROR_REFERENCE, "cannot delete HEAD");
147 return GIT_ERROR;
148 }
149
150 if (ref->type == GIT_REFERENCE_DIRECT)
151 old_id = &ref->target.oid;
152 else
153 old_target = ref->target.symbolic;
154
155 return git_refdb_delete(ref->db, ref->name, old_id, old_target);
156 }
157
git_reference_remove(git_repository * repo,const char * name)158 int git_reference_remove(git_repository *repo, const char *name)
159 {
160 git_refdb *db;
161 int error;
162
163 if ((error = git_repository_refdb__weakptr(&db, repo)) < 0)
164 return error;
165
166 return git_refdb_delete(db, name, NULL, NULL);
167 }
168
git_reference_lookup(git_reference ** ref_out,git_repository * repo,const char * name)169 int git_reference_lookup(git_reference **ref_out,
170 git_repository *repo, const char *name)
171 {
172 return git_reference_lookup_resolved(ref_out, repo, name, 0);
173 }
174
git_reference_name_to_id(git_oid * out,git_repository * repo,const char * name)175 int git_reference_name_to_id(
176 git_oid *out, git_repository *repo, const char *name)
177 {
178 int error;
179 git_reference *ref;
180
181 if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0)
182 return error;
183
184 git_oid_cpy(out, git_reference_target(ref));
185 git_reference_free(ref);
186 return 0;
187 }
188
reference_normalize_for_repo(git_refname_t out,git_repository * repo,const char * name,bool validate)189 static int reference_normalize_for_repo(
190 git_refname_t out,
191 git_repository *repo,
192 const char *name,
193 bool validate)
194 {
195 int precompose;
196 unsigned int flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL;
197
198 if (!git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) &&
199 precompose)
200 flags |= GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE;
201
202 if (!validate)
203 flags |= GIT_REFERENCE_FORMAT__VALIDATION_DISABLE;
204
205 return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags);
206 }
207
git_reference_lookup_resolved(git_reference ** ref_out,git_repository * repo,const char * name,int max_nesting)208 int git_reference_lookup_resolved(
209 git_reference **ref_out,
210 git_repository *repo,
211 const char *name,
212 int max_nesting)
213 {
214 git_refname_t normalized;
215 git_refdb *refdb;
216 int error = 0;
217
218 assert(ref_out && repo && name);
219
220 if ((error = reference_normalize_for_repo(normalized, repo, name, true)) < 0 ||
221 (error = git_repository_refdb__weakptr(&refdb, repo)) < 0 ||
222 (error = git_refdb_resolve(ref_out, refdb, normalized, max_nesting)) < 0)
223 return error;
224
225 /*
226 * The resolved reference may be a symbolic reference in case its
227 * target doesn't exist. If the user asked us to resolve (e.g.
228 * `max_nesting != 0`), then we need to return an error in case we got
229 * a symbolic reference back.
230 */
231 if (max_nesting && git_reference_type(*ref_out) == GIT_REFERENCE_SYMBOLIC) {
232 git_reference_free(*ref_out);
233 *ref_out = NULL;
234 return GIT_ENOTFOUND;
235 }
236
237 return 0;
238 }
239
git_reference__read_head(git_reference ** out,git_repository * repo,const char * path)240 int git_reference__read_head(
241 git_reference **out,
242 git_repository *repo,
243 const char *path)
244 {
245 git_buf reference = GIT_BUF_INIT;
246 char *name = NULL;
247 int error;
248
249 if ((error = git_futils_readbuffer(&reference, path)) < 0)
250 goto out;
251 git_buf_rtrim(&reference);
252
253 if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF)) == 0) {
254 git_buf_consume(&reference, reference.ptr + strlen(GIT_SYMREF));
255
256 name = git_path_basename(path);
257
258 if ((*out = git_reference__alloc_symbolic(name, reference.ptr)) == NULL) {
259 error = -1;
260 goto out;
261 }
262 } else {
263 if ((error = git_reference_lookup(out, repo, reference.ptr)) < 0)
264 goto out;
265 }
266
267 out:
268 git__free(name);
269 git_buf_dispose(&reference);
270
271 return error;
272 }
273
git_reference_dwim(git_reference ** out,git_repository * repo,const char * refname)274 int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname)
275 {
276 int error = 0, i;
277 bool fallbackmode = true, foundvalid = false;
278 git_reference *ref;
279 git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;
280
281 static const char* formatters[] = {
282 "%s",
283 GIT_REFS_DIR "%s",
284 GIT_REFS_TAGS_DIR "%s",
285 GIT_REFS_HEADS_DIR "%s",
286 GIT_REFS_REMOTES_DIR "%s",
287 GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE,
288 NULL
289 };
290
291 if (*refname)
292 git_buf_puts(&name, refname);
293 else {
294 git_buf_puts(&name, GIT_HEAD_FILE);
295 fallbackmode = false;
296 }
297
298 for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) {
299
300 git_buf_clear(&refnamebuf);
301
302 if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0)
303 goto cleanup;
304
305 if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) {
306 error = GIT_EINVALIDSPEC;
307 continue;
308 }
309 foundvalid = true;
310
311 error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1);
312
313 if (!error) {
314 *out = ref;
315 error = 0;
316 goto cleanup;
317 }
318
319 if (error != GIT_ENOTFOUND)
320 goto cleanup;
321 }
322
323 cleanup:
324 if (error && !foundvalid) {
325 /* never found a valid reference name */
326 git_error_set(GIT_ERROR_REFERENCE,
327 "could not use '%s' as valid reference name", git_buf_cstr(&name));
328 }
329
330 if (error == GIT_ENOTFOUND)
331 git_error_set(GIT_ERROR_REFERENCE, "no reference found for shorthand '%s'", refname);
332
333 git_buf_dispose(&name);
334 git_buf_dispose(&refnamebuf);
335 return error;
336 }
337
338 /**
339 * Getters
340 */
git_reference_type(const git_reference * ref)341 git_reference_t git_reference_type(const git_reference *ref)
342 {
343 assert(ref);
344 return ref->type;
345 }
346
git_reference_name(const git_reference * ref)347 const char *git_reference_name(const git_reference *ref)
348 {
349 assert(ref);
350 return ref->name;
351 }
352
git_reference_owner(const git_reference * ref)353 git_repository *git_reference_owner(const git_reference *ref)
354 {
355 assert(ref);
356 return ref->db->repo;
357 }
358
git_reference_target(const git_reference * ref)359 const git_oid *git_reference_target(const git_reference *ref)
360 {
361 assert(ref);
362
363 if (ref->type != GIT_REFERENCE_DIRECT)
364 return NULL;
365
366 return &ref->target.oid;
367 }
368
git_reference_target_peel(const git_reference * ref)369 const git_oid *git_reference_target_peel(const git_reference *ref)
370 {
371 assert(ref);
372
373 if (ref->type != GIT_REFERENCE_DIRECT || git_oid_is_zero(&ref->peel))
374 return NULL;
375
376 return &ref->peel;
377 }
378
git_reference_symbolic_target(const git_reference * ref)379 const char *git_reference_symbolic_target(const git_reference *ref)
380 {
381 assert(ref);
382
383 if (ref->type != GIT_REFERENCE_SYMBOLIC)
384 return NULL;
385
386 return ref->target.symbolic;
387 }
388
reference__create(git_reference ** ref_out,git_repository * repo,const char * name,const git_oid * oid,const char * symbolic,int force,const git_signature * signature,const char * log_message,const git_oid * old_id,const char * old_target)389 static int reference__create(
390 git_reference **ref_out,
391 git_repository *repo,
392 const char *name,
393 const git_oid *oid,
394 const char *symbolic,
395 int force,
396 const git_signature *signature,
397 const char *log_message,
398 const git_oid *old_id,
399 const char *old_target)
400 {
401 git_refname_t normalized;
402 git_refdb *refdb;
403 git_reference *ref = NULL;
404 int error = 0;
405
406 assert(repo && name);
407 assert(symbolic || signature);
408
409 if (ref_out)
410 *ref_out = NULL;
411
412 error = reference_normalize_for_repo(normalized, repo, name, true);
413 if (error < 0)
414 return error;
415
416 error = git_repository_refdb__weakptr(&refdb, repo);
417 if (error < 0)
418 return error;
419
420 if (oid != NULL) {
421 assert(symbolic == NULL);
422
423 if (!git_object__is_valid(repo, oid, GIT_OBJECT_ANY)) {
424 git_error_set(GIT_ERROR_REFERENCE,
425 "target OID for the reference doesn't exist on the repository");
426 return -1;
427 }
428
429 ref = git_reference__alloc(normalized, oid, NULL);
430 } else {
431 git_refname_t normalized_target;
432
433 error = reference_normalize_for_repo(normalized_target, repo,
434 symbolic, git_reference__enable_symbolic_ref_target_validation);
435
436 if (error < 0)
437 return error;
438
439 ref = git_reference__alloc_symbolic(normalized, normalized_target);
440 }
441
442 GIT_ERROR_CHECK_ALLOC(ref);
443
444 if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id, old_target)) < 0) {
445 git_reference_free(ref);
446 return error;
447 }
448
449 if (ref_out == NULL)
450 git_reference_free(ref);
451 else
452 *ref_out = ref;
453
454 return 0;
455 }
456
refs_configured_ident(git_signature ** out,const git_repository * repo)457 static int refs_configured_ident(git_signature **out, const git_repository *repo)
458 {
459 if (repo->ident_name && repo->ident_email)
460 return git_signature_now(out, repo->ident_name, repo->ident_email);
461
462 /* if not configured let us fall-through to the next method */
463 return -1;
464 }
465
git_reference__log_signature(git_signature ** out,git_repository * repo)466 int git_reference__log_signature(git_signature **out, git_repository *repo)
467 {
468 int error;
469 git_signature *who;
470
471 if(((error = refs_configured_ident(&who, repo)) < 0) &&
472 ((error = git_signature_default(&who, repo)) < 0) &&
473 ((error = git_signature_now(&who, "unknown", "unknown")) < 0))
474 return error;
475
476 *out = who;
477 return 0;
478 }
479
git_reference_create_matching(git_reference ** ref_out,git_repository * repo,const char * name,const git_oid * id,int force,const git_oid * old_id,const char * log_message)480 int git_reference_create_matching(
481 git_reference **ref_out,
482 git_repository *repo,
483 const char *name,
484 const git_oid *id,
485 int force,
486 const git_oid *old_id,
487 const char *log_message)
488
489 {
490 int error;
491 git_signature *who = NULL;
492
493 assert(id);
494
495 if ((error = git_reference__log_signature(&who, repo)) < 0)
496 return error;
497
498 error = reference__create(
499 ref_out, repo, name, id, NULL, force, who, log_message, old_id, NULL);
500
501 git_signature_free(who);
502 return error;
503 }
504
git_reference_create(git_reference ** ref_out,git_repository * repo,const char * name,const git_oid * id,int force,const char * log_message)505 int git_reference_create(
506 git_reference **ref_out,
507 git_repository *repo,
508 const char *name,
509 const git_oid *id,
510 int force,
511 const char *log_message)
512 {
513 return git_reference_create_matching(ref_out, repo, name, id, force, NULL, log_message);
514 }
515
git_reference_symbolic_create_matching(git_reference ** ref_out,git_repository * repo,const char * name,const char * target,int force,const char * old_target,const char * log_message)516 int git_reference_symbolic_create_matching(
517 git_reference **ref_out,
518 git_repository *repo,
519 const char *name,
520 const char *target,
521 int force,
522 const char *old_target,
523 const char *log_message)
524 {
525 int error;
526 git_signature *who = NULL;
527
528 assert(target);
529
530 if ((error = git_reference__log_signature(&who, repo)) < 0)
531 return error;
532
533 error = reference__create(
534 ref_out, repo, name, NULL, target, force, who, log_message, NULL, old_target);
535
536 git_signature_free(who);
537 return error;
538 }
539
git_reference_symbolic_create(git_reference ** ref_out,git_repository * repo,const char * name,const char * target,int force,const char * log_message)540 int git_reference_symbolic_create(
541 git_reference **ref_out,
542 git_repository *repo,
543 const char *name,
544 const char *target,
545 int force,
546 const char *log_message)
547 {
548 return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, NULL, log_message);
549 }
550
ensure_is_an_updatable_direct_reference(git_reference * ref)551 static int ensure_is_an_updatable_direct_reference(git_reference *ref)
552 {
553 if (ref->type == GIT_REFERENCE_DIRECT)
554 return 0;
555
556 git_error_set(GIT_ERROR_REFERENCE, "cannot set OID on symbolic reference");
557 return -1;
558 }
559
git_reference_set_target(git_reference ** out,git_reference * ref,const git_oid * id,const char * log_message)560 int git_reference_set_target(
561 git_reference **out,
562 git_reference *ref,
563 const git_oid *id,
564 const char *log_message)
565 {
566 int error;
567 git_repository *repo;
568
569 assert(out && ref && id);
570
571 repo = ref->db->repo;
572
573 if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)
574 return error;
575
576 return git_reference_create_matching(out, repo, ref->name, id, 1, &ref->target.oid, log_message);
577 }
578
ensure_is_an_updatable_symbolic_reference(git_reference * ref)579 static int ensure_is_an_updatable_symbolic_reference(git_reference *ref)
580 {
581 if (ref->type == GIT_REFERENCE_SYMBOLIC)
582 return 0;
583
584 git_error_set(GIT_ERROR_REFERENCE, "cannot set symbolic target on a direct reference");
585 return -1;
586 }
587
git_reference_symbolic_set_target(git_reference ** out,git_reference * ref,const char * target,const char * log_message)588 int git_reference_symbolic_set_target(
589 git_reference **out,
590 git_reference *ref,
591 const char *target,
592 const char *log_message)
593 {
594 int error;
595
596 assert(out && ref && target);
597
598 if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0)
599 return error;
600
601 return git_reference_symbolic_create_matching(
602 out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, log_message);
603 }
604
605 typedef struct {
606 const char *old_name;
607 git_refname_t new_name;
608 } rename_cb_data;
609
update_wt_heads(git_repository * repo,const char * path,void * payload)610 static int update_wt_heads(git_repository *repo, const char *path, void *payload)
611 {
612 rename_cb_data *data = (rename_cb_data *) payload;
613 git_reference *head = NULL;
614 char *gitdir = NULL;
615 int error;
616
617 if ((error = git_reference__read_head(&head, repo, path)) < 0) {
618 git_error_set(GIT_ERROR_REFERENCE, "could not read HEAD when renaming references");
619 goto out;
620 }
621
622 if ((gitdir = git_path_dirname(path)) == NULL) {
623 error = -1;
624 goto out;
625 }
626
627 if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC ||
628 git__strcmp(head->target.symbolic, data->old_name) != 0) {
629 error = 0;
630 goto out;
631 }
632
633 /* Update HEAD it was pointing to the reference being renamed */
634 if ((error = git_repository_create_head(gitdir, data->new_name)) < 0) {
635 git_error_set(GIT_ERROR_REFERENCE, "failed to update HEAD after renaming reference");
636 goto out;
637 }
638
639 out:
640 git_reference_free(head);
641 git__free(gitdir);
642
643 return error;
644 }
645
reference__rename(git_reference ** out,git_reference * ref,const char * new_name,int force,const git_signature * signature,const char * message)646 static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force,
647 const git_signature *signature, const char *message)
648 {
649 git_repository *repo;
650 git_refname_t normalized;
651 bool should_head_be_updated = false;
652 int error = 0;
653
654 assert(ref && new_name && signature);
655
656 repo = git_reference_owner(ref);
657
658 if ((error = reference_normalize_for_repo(
659 normalized, repo, new_name, true)) < 0)
660 return error;
661
662 /* Check if we have to update HEAD. */
663 if ((error = git_branch_is_head(ref)) < 0)
664 return error;
665
666 should_head_be_updated = (error > 0);
667
668 if ((error = git_refdb_rename(out, ref->db, ref->name, normalized, force, signature, message)) < 0)
669 return error;
670
671 /* Update HEAD if it was pointing to the reference being renamed */
672 if (should_head_be_updated) {
673 error = git_repository_set_head(ref->db->repo, normalized);
674 } else {
675 rename_cb_data payload;
676 payload.old_name = ref->name;
677 memcpy(&payload.new_name, &normalized, sizeof(normalized));
678
679 error = git_repository_foreach_head(repo, update_wt_heads, 0, &payload);
680 }
681
682 return error;
683 }
684
685
git_reference_rename(git_reference ** out,git_reference * ref,const char * new_name,int force,const char * log_message)686 int git_reference_rename(
687 git_reference **out,
688 git_reference *ref,
689 const char *new_name,
690 int force,
691 const char *log_message)
692 {
693 git_signature *who;
694 int error;
695
696 assert(out && ref);
697
698 if ((error = git_reference__log_signature(&who, ref->db->repo)) < 0)
699 return error;
700
701 error = reference__rename(out, ref, new_name, force, who, log_message);
702 git_signature_free(who);
703
704 return error;
705 }
706
git_reference_resolve(git_reference ** ref_out,const git_reference * ref)707 int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
708 {
709 switch (git_reference_type(ref)) {
710 case GIT_REFERENCE_DIRECT:
711 return git_reference_lookup(ref_out, ref->db->repo, ref->name);
712
713 case GIT_REFERENCE_SYMBOLIC:
714 return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1);
715
716 default:
717 git_error_set(GIT_ERROR_REFERENCE, "invalid reference");
718 return -1;
719 }
720 }
721
git_reference_foreach(git_repository * repo,git_reference_foreach_cb callback,void * payload)722 int git_reference_foreach(
723 git_repository *repo,
724 git_reference_foreach_cb callback,
725 void *payload)
726 {
727 git_reference_iterator *iter;
728 git_reference *ref;
729 int error;
730
731 if ((error = git_reference_iterator_new(&iter, repo)) < 0)
732 return error;
733
734 while (!(error = git_reference_next(&ref, iter))) {
735 if ((error = callback(ref, payload)) != 0) {
736 git_error_set_after_callback(error);
737 break;
738 }
739 }
740
741 if (error == GIT_ITEROVER)
742 error = 0;
743
744 git_reference_iterator_free(iter);
745 return error;
746 }
747
git_reference_foreach_name(git_repository * repo,git_reference_foreach_name_cb callback,void * payload)748 int git_reference_foreach_name(
749 git_repository *repo,
750 git_reference_foreach_name_cb callback,
751 void *payload)
752 {
753 git_reference_iterator *iter;
754 const char *refname;
755 int error;
756
757 if ((error = git_reference_iterator_new(&iter, repo)) < 0)
758 return error;
759
760 while (!(error = git_reference_next_name(&refname, iter))) {
761 if ((error = callback(refname, payload)) != 0) {
762 git_error_set_after_callback(error);
763 break;
764 }
765 }
766
767 if (error == GIT_ITEROVER)
768 error = 0;
769
770 git_reference_iterator_free(iter);
771 return error;
772 }
773
git_reference_foreach_glob(git_repository * repo,const char * glob,git_reference_foreach_name_cb callback,void * payload)774 int git_reference_foreach_glob(
775 git_repository *repo,
776 const char *glob,
777 git_reference_foreach_name_cb callback,
778 void *payload)
779 {
780 git_reference_iterator *iter;
781 const char *refname;
782 int error;
783
784 if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0)
785 return error;
786
787 while (!(error = git_reference_next_name(&refname, iter))) {
788 if ((error = callback(refname, payload)) != 0) {
789 git_error_set_after_callback(error);
790 break;
791 }
792 }
793
794 if (error == GIT_ITEROVER)
795 error = 0;
796
797 git_reference_iterator_free(iter);
798 return error;
799 }
800
git_reference_iterator_new(git_reference_iterator ** out,git_repository * repo)801 int git_reference_iterator_new(git_reference_iterator **out, git_repository *repo)
802 {
803 git_refdb *refdb;
804
805 if (git_repository_refdb__weakptr(&refdb, repo) < 0)
806 return -1;
807
808 return git_refdb_iterator(out, refdb, NULL);
809 }
810
git_reference_iterator_glob_new(git_reference_iterator ** out,git_repository * repo,const char * glob)811 int git_reference_iterator_glob_new(
812 git_reference_iterator **out, git_repository *repo, const char *glob)
813 {
814 git_refdb *refdb;
815
816 if (git_repository_refdb__weakptr(&refdb, repo) < 0)
817 return -1;
818
819 return git_refdb_iterator(out, refdb, glob);
820 }
821
git_reference_next(git_reference ** out,git_reference_iterator * iter)822 int git_reference_next(git_reference **out, git_reference_iterator *iter)
823 {
824 return git_refdb_iterator_next(out, iter);
825 }
826
git_reference_next_name(const char ** out,git_reference_iterator * iter)827 int git_reference_next_name(const char **out, git_reference_iterator *iter)
828 {
829 return git_refdb_iterator_next_name(out, iter);
830 }
831
git_reference_iterator_free(git_reference_iterator * iter)832 void git_reference_iterator_free(git_reference_iterator *iter)
833 {
834 if (iter == NULL)
835 return;
836
837 git_refdb_iterator_free(iter);
838 }
839
cb__reflist_add(const char * ref,void * data)840 static int cb__reflist_add(const char *ref, void *data)
841 {
842 char *name = git__strdup(ref);
843 GIT_ERROR_CHECK_ALLOC(name);
844 return git_vector_insert((git_vector *)data, name);
845 }
846
git_reference_list(git_strarray * array,git_repository * repo)847 int git_reference_list(
848 git_strarray *array,
849 git_repository *repo)
850 {
851 git_vector ref_list;
852
853 assert(array && repo);
854
855 array->strings = NULL;
856 array->count = 0;
857
858 if (git_vector_init(&ref_list, 8, NULL) < 0)
859 return -1;
860
861 if (git_reference_foreach_name(
862 repo, &cb__reflist_add, (void *)&ref_list) < 0) {
863 git_vector_free(&ref_list);
864 return -1;
865 }
866
867 array->strings = (char **)git_vector_detach(&array->count, NULL, &ref_list);
868
869 return 0;
870 }
871
is_valid_ref_char(char ch)872 static int is_valid_ref_char(char ch)
873 {
874 if ((unsigned) ch <= ' ')
875 return 0;
876
877 switch (ch) {
878 case '~':
879 case '^':
880 case ':':
881 case '\\':
882 case '?':
883 case '[':
884 return 0;
885 default:
886 return 1;
887 }
888 }
889
ensure_segment_validity(const char * name,char may_contain_glob)890 static int ensure_segment_validity(const char *name, char may_contain_glob)
891 {
892 const char *current = name;
893 char prev = '\0';
894 const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION);
895 int segment_len;
896
897 if (*current == '.')
898 return -1; /* Refname starts with "." */
899
900 for (current = name; ; current++) {
901 if (*current == '\0' || *current == '/')
902 break;
903
904 if (!is_valid_ref_char(*current))
905 return -1; /* Illegal character in refname */
906
907 if (prev == '.' && *current == '.')
908 return -1; /* Refname contains ".." */
909
910 if (prev == '@' && *current == '{')
911 return -1; /* Refname contains "@{" */
912
913 if (*current == '*') {
914 if (!may_contain_glob)
915 return -1;
916 may_contain_glob = 0;
917 }
918
919 prev = *current;
920 }
921
922 segment_len = (int)(current - name);
923
924 /* A refname component can not end with ".lock" */
925 if (segment_len >= lock_len &&
926 !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len))
927 return -1;
928
929 return segment_len;
930 }
931
is_all_caps_and_underscore(const char * name,size_t len)932 static bool is_all_caps_and_underscore(const char *name, size_t len)
933 {
934 size_t i;
935 char c;
936
937 assert(name && len > 0);
938
939 for (i = 0; i < len; i++)
940 {
941 c = name[i];
942 if ((c < 'A' || c > 'Z') && c != '_')
943 return false;
944 }
945
946 if (*name == '_' || name[len - 1] == '_')
947 return false;
948
949 return true;
950 }
951
952 /* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */
git_reference__normalize_name(git_buf * buf,const char * name,unsigned int flags)953 int git_reference__normalize_name(
954 git_buf *buf,
955 const char *name,
956 unsigned int flags)
957 {
958 const char *current;
959 int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
960 unsigned int process_flags;
961 bool normalize = (buf != NULL);
962 bool validate = (flags & GIT_REFERENCE_FORMAT__VALIDATION_DISABLE) == 0;
963
964 #ifdef GIT_USE_ICONV
965 git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
966 #endif
967
968 assert(name);
969
970 process_flags = flags;
971 current = (char *)name;
972
973 if (validate && *current == '/')
974 goto cleanup;
975
976 if (normalize)
977 git_buf_clear(buf);
978
979 #ifdef GIT_USE_ICONV
980 if ((flags & GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE) != 0) {
981 size_t namelen = strlen(current);
982 if ((error = git_path_iconv_init_precompose(&ic)) < 0 ||
983 (error = git_path_iconv(&ic, ¤t, &namelen)) < 0)
984 goto cleanup;
985 error = GIT_EINVALIDSPEC;
986 }
987 #endif
988
989 if (!validate) {
990 git_buf_sets(buf, current);
991
992 error = git_buf_oom(buf) ? -1 : 0;
993 goto cleanup;
994 }
995
996 while (true) {
997 char may_contain_glob = process_flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN;
998
999 segment_len = ensure_segment_validity(current, may_contain_glob);
1000 if (segment_len < 0)
1001 goto cleanup;
1002
1003 if (segment_len > 0) {
1004 /*
1005 * There may only be one glob in a pattern, thus we reset
1006 * the pattern-flag in case the current segment has one.
1007 */
1008 if (memchr(current, '*', segment_len))
1009 process_flags &= ~GIT_REFERENCE_FORMAT_REFSPEC_PATTERN;
1010
1011 if (normalize) {
1012 size_t cur_len = git_buf_len(buf);
1013
1014 git_buf_joinpath(buf, git_buf_cstr(buf), current);
1015 git_buf_truncate(buf,
1016 cur_len + segment_len + (segments_count ? 1 : 0));
1017
1018 if (git_buf_oom(buf)) {
1019 error = -1;
1020 goto cleanup;
1021 }
1022 }
1023
1024 segments_count++;
1025 }
1026
1027 /* No empty segment is allowed when not normalizing */
1028 if (segment_len == 0 && !normalize)
1029 goto cleanup;
1030
1031 if (current[segment_len] == '\0')
1032 break;
1033
1034 current += segment_len + 1;
1035 }
1036
1037 /* A refname can not be empty */
1038 if (segment_len == 0 && segments_count == 0)
1039 goto cleanup;
1040
1041 /* A refname can not end with "." */
1042 if (current[segment_len - 1] == '.')
1043 goto cleanup;
1044
1045 /* A refname can not end with "/" */
1046 if (current[segment_len - 1] == '/')
1047 goto cleanup;
1048
1049 if ((segments_count == 1 ) && !(flags & GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL))
1050 goto cleanup;
1051
1052 if ((segments_count == 1 ) &&
1053 !(flags & GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND) &&
1054 !(is_all_caps_and_underscore(name, (size_t)segment_len) ||
1055 ((flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
1056 goto cleanup;
1057
1058 if ((segments_count > 1)
1059 && (is_all_caps_and_underscore(name, strchr(name, '/') - name)))
1060 goto cleanup;
1061
1062 error = 0;
1063
1064 cleanup:
1065 if (error == GIT_EINVALIDSPEC)
1066 git_error_set(
1067 GIT_ERROR_REFERENCE,
1068 "the given reference name '%s' is not valid", name);
1069
1070 if (error && normalize)
1071 git_buf_dispose(buf);
1072
1073 #ifdef GIT_USE_ICONV
1074 git_path_iconv_clear(&ic);
1075 #endif
1076
1077 return error;
1078 }
1079
git_reference_normalize_name(char * buffer_out,size_t buffer_size,const char * name,unsigned int flags)1080 int git_reference_normalize_name(
1081 char *buffer_out,
1082 size_t buffer_size,
1083 const char *name,
1084 unsigned int flags)
1085 {
1086 git_buf buf = GIT_BUF_INIT;
1087 int error;
1088
1089 if ((error = git_reference__normalize_name(&buf, name, flags)) < 0)
1090 goto cleanup;
1091
1092 if (git_buf_len(&buf) > buffer_size - 1) {
1093 git_error_set(
1094 GIT_ERROR_REFERENCE,
1095 "the provided buffer is too short to hold the normalization of '%s'", name);
1096 error = GIT_EBUFS;
1097 goto cleanup;
1098 }
1099
1100 git_buf_copy_cstr(buffer_out, buffer_size, &buf);
1101
1102 error = 0;
1103
1104 cleanup:
1105 git_buf_dispose(&buf);
1106 return error;
1107 }
1108
1109 #define GIT_REFERENCE_TYPEMASK (GIT_REFERENCE_DIRECT | GIT_REFERENCE_SYMBOLIC)
1110
git_reference_cmp(const git_reference * ref1,const git_reference * ref2)1111 int git_reference_cmp(
1112 const git_reference *ref1,
1113 const git_reference *ref2)
1114 {
1115 git_reference_t type1, type2;
1116 assert(ref1 && ref2);
1117
1118 type1 = git_reference_type(ref1);
1119 type2 = git_reference_type(ref2);
1120
1121 /* let's put symbolic refs before OIDs */
1122 if (type1 != type2)
1123 return (type1 == GIT_REFERENCE_SYMBOLIC) ? -1 : 1;
1124
1125 if (type1 == GIT_REFERENCE_SYMBOLIC)
1126 return strcmp(ref1->target.symbolic, ref2->target.symbolic);
1127
1128 return git_oid__cmp(&ref1->target.oid, &ref2->target.oid);
1129 }
1130
1131 /*
1132 * Starting with the reference given by `ref_name`, follows symbolic
1133 * references until a direct reference is found and updated the OID
1134 * on that direct reference to `oid`.
1135 */
git_reference__update_terminal(git_repository * repo,const char * ref_name,const git_oid * oid,const git_signature * sig,const char * log_message)1136 int git_reference__update_terminal(
1137 git_repository *repo,
1138 const char *ref_name,
1139 const git_oid *oid,
1140 const git_signature *sig,
1141 const char *log_message)
1142 {
1143 git_reference *ref = NULL, *ref2 = NULL;
1144 git_signature *who = NULL;
1145 git_refdb *refdb = NULL;
1146 const git_signature *to_use;
1147 int error = 0;
1148
1149 if (!sig && (error = git_reference__log_signature(&who, repo)) < 0)
1150 goto out;
1151
1152 to_use = sig ? sig : who;
1153
1154 if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
1155 goto out;
1156
1157 if ((error = git_refdb_resolve(&ref, refdb, ref_name, -1)) < 0) {
1158 if (error == GIT_ENOTFOUND) {
1159 git_error_clear();
1160 error = reference__create(&ref2, repo, ref_name, oid, NULL, 0, to_use,
1161 log_message, NULL, NULL);
1162 }
1163 goto out;
1164 }
1165
1166 /* In case the resolved reference is symbolic, then it's a dangling symref. */
1167 if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) {
1168 error = reference__create(&ref2, repo, ref->target.symbolic, oid, NULL, 0, to_use,
1169 log_message, NULL, NULL);
1170 } else {
1171 error = reference__create(&ref2, repo, ref->name, oid, NULL, 1, to_use,
1172 log_message, &ref->target.oid, NULL);
1173 }
1174
1175 out:
1176 git_reference_free(ref2);
1177 git_reference_free(ref);
1178 git_signature_free(who);
1179 return error;
1180 }
1181
commit_type(const git_commit * commit)1182 static const char *commit_type(const git_commit *commit)
1183 {
1184 unsigned int count = git_commit_parentcount(commit);
1185
1186 if (count >= 2)
1187 return " (merge)";
1188 else if (count == 0)
1189 return " (initial)";
1190 else
1191 return "";
1192 }
1193
git_reference__update_for_commit(git_repository * repo,git_reference * ref,const char * ref_name,const git_oid * id,const char * operation)1194 int git_reference__update_for_commit(
1195 git_repository *repo,
1196 git_reference *ref,
1197 const char *ref_name,
1198 const git_oid *id,
1199 const char *operation)
1200 {
1201 git_reference *ref_new = NULL;
1202 git_commit *commit = NULL;
1203 git_buf reflog_msg = GIT_BUF_INIT;
1204 const git_signature *who;
1205 int error;
1206
1207 if ((error = git_commit_lookup(&commit, repo, id)) < 0 ||
1208 (error = git_buf_printf(&reflog_msg, "%s%s: %s",
1209 operation ? operation : "commit",
1210 commit_type(commit),
1211 git_commit_summary(commit))) < 0)
1212 goto done;
1213
1214 who = git_commit_committer(commit);
1215
1216 if (ref) {
1217 if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)
1218 return error;
1219
1220 error = reference__create(&ref_new, repo, ref->name, id, NULL, 1, who,
1221 git_buf_cstr(&reflog_msg), &ref->target.oid, NULL);
1222 }
1223 else
1224 error = git_reference__update_terminal(
1225 repo, ref_name, id, who, git_buf_cstr(&reflog_msg));
1226
1227 done:
1228 git_reference_free(ref_new);
1229 git_buf_dispose(&reflog_msg);
1230 git_commit_free(commit);
1231 return error;
1232 }
1233
git_reference_has_log(git_repository * repo,const char * refname)1234 int git_reference_has_log(git_repository *repo, const char *refname)
1235 {
1236 int error;
1237 git_refdb *refdb;
1238
1239 assert(repo && refname);
1240
1241 if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
1242 return error;
1243
1244 return git_refdb_has_log(refdb, refname);
1245 }
1246
git_reference_ensure_log(git_repository * repo,const char * refname)1247 int git_reference_ensure_log(git_repository *repo, const char *refname)
1248 {
1249 int error;
1250 git_refdb *refdb;
1251
1252 assert(repo && refname);
1253
1254 if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
1255 return error;
1256
1257 return git_refdb_ensure_log(refdb, refname);
1258 }
1259
git_reference__is_branch(const char * ref_name)1260 int git_reference__is_branch(const char *ref_name)
1261 {
1262 return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
1263 }
1264
git_reference_is_branch(const git_reference * ref)1265 int git_reference_is_branch(const git_reference *ref)
1266 {
1267 assert(ref);
1268 return git_reference__is_branch(ref->name);
1269 }
1270
git_reference__is_remote(const char * ref_name)1271 int git_reference__is_remote(const char *ref_name)
1272 {
1273 return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0;
1274 }
1275
git_reference_is_remote(const git_reference * ref)1276 int git_reference_is_remote(const git_reference *ref)
1277 {
1278 assert(ref);
1279 return git_reference__is_remote(ref->name);
1280 }
1281
git_reference__is_tag(const char * ref_name)1282 int git_reference__is_tag(const char *ref_name)
1283 {
1284 return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0;
1285 }
1286
git_reference_is_tag(const git_reference * ref)1287 int git_reference_is_tag(const git_reference *ref)
1288 {
1289 assert(ref);
1290 return git_reference__is_tag(ref->name);
1291 }
1292
git_reference__is_note(const char * ref_name)1293 int git_reference__is_note(const char *ref_name)
1294 {
1295 return git__prefixcmp(ref_name, GIT_REFS_NOTES_DIR) == 0;
1296 }
1297
git_reference_is_note(const git_reference * ref)1298 int git_reference_is_note(const git_reference *ref)
1299 {
1300 assert(ref);
1301 return git_reference__is_note(ref->name);
1302 }
1303
peel_error(int error,const git_reference * ref,const char * msg)1304 static int peel_error(int error, const git_reference *ref, const char* msg)
1305 {
1306 git_error_set(
1307 GIT_ERROR_INVALID,
1308 "the reference '%s' cannot be peeled - %s", git_reference_name(ref), msg);
1309 return error;
1310 }
1311
git_reference_peel(git_object ** peeled,const git_reference * ref,git_object_t target_type)1312 int git_reference_peel(
1313 git_object **peeled,
1314 const git_reference *ref,
1315 git_object_t target_type)
1316 {
1317 const git_reference *resolved = NULL;
1318 git_reference *allocated = NULL;
1319 git_object *target = NULL;
1320 int error;
1321
1322 assert(ref);
1323
1324 if (ref->type == GIT_REFERENCE_DIRECT) {
1325 resolved = ref;
1326 } else {
1327 if ((error = git_reference_resolve(&allocated, ref)) < 0)
1328 return peel_error(error, ref, "Cannot resolve reference");
1329
1330 resolved = allocated;
1331 }
1332
1333 /*
1334 * If we try to peel an object to a tag, we cannot use
1335 * the fully peeled object, as that will always resolve
1336 * to a commit. So we only want to use the peeled value
1337 * if it is not zero and the target is not a tag.
1338 */
1339 if (target_type != GIT_OBJECT_TAG && !git_oid_is_zero(&resolved->peel)) {
1340 error = git_object_lookup(&target,
1341 git_reference_owner(ref), &resolved->peel, GIT_OBJECT_ANY);
1342 } else {
1343 error = git_object_lookup(&target,
1344 git_reference_owner(ref), &resolved->target.oid, GIT_OBJECT_ANY);
1345 }
1346
1347 if (error < 0) {
1348 peel_error(error, ref, "Cannot retrieve reference target");
1349 goto cleanup;
1350 }
1351
1352 if (target_type == GIT_OBJECT_ANY && git_object_type(target) != GIT_OBJECT_TAG)
1353 error = git_object_dup(peeled, target);
1354 else
1355 error = git_object_peel(peeled, target, target_type);
1356
1357 cleanup:
1358 git_object_free(target);
1359 git_reference_free(allocated);
1360
1361 return error;
1362 }
1363
git_reference__is_valid_name(const char * refname,unsigned int flags)1364 int git_reference__is_valid_name(const char *refname, unsigned int flags)
1365 {
1366 if (git_reference__normalize_name(NULL, refname, flags) < 0) {
1367 git_error_clear();
1368 return false;
1369 }
1370
1371 return true;
1372 }
1373
git_reference_is_valid_name(const char * refname)1374 int git_reference_is_valid_name(const char *refname)
1375 {
1376 return git_reference__is_valid_name(refname, GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL);
1377 }
1378
git_reference__shorthand(const char * name)1379 const char *git_reference__shorthand(const char *name)
1380 {
1381 if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR))
1382 return name + strlen(GIT_REFS_HEADS_DIR);
1383 else if (!git__prefixcmp(name, GIT_REFS_TAGS_DIR))
1384 return name + strlen(GIT_REFS_TAGS_DIR);
1385 else if (!git__prefixcmp(name, GIT_REFS_REMOTES_DIR))
1386 return name + strlen(GIT_REFS_REMOTES_DIR);
1387 else if (!git__prefixcmp(name, GIT_REFS_DIR))
1388 return name + strlen(GIT_REFS_DIR);
1389
1390 /* No shorthands are avaiable, so just return the name */
1391 return name;
1392 }
1393
git_reference_shorthand(const git_reference * ref)1394 const char *git_reference_shorthand(const git_reference *ref)
1395 {
1396 return git_reference__shorthand(ref->name);
1397 }
1398
git_reference__is_unborn_head(bool * unborn,const git_reference * ref,git_repository * repo)1399 int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo)
1400 {
1401 int error;
1402 git_reference *tmp_ref;
1403 assert(unborn && ref && repo);
1404
1405 if (ref->type == GIT_REFERENCE_DIRECT) {
1406 *unborn = 0;
1407 return 0;
1408 }
1409
1410 error = git_reference_lookup_resolved(&tmp_ref, repo, ref->name, -1);
1411 git_reference_free(tmp_ref);
1412
1413 if (error != 0 && error != GIT_ENOTFOUND)
1414 return error;
1415 else if (error == GIT_ENOTFOUND && git__strcmp(ref->name, GIT_HEAD_FILE) == 0)
1416 *unborn = true;
1417 else
1418 *unborn = false;
1419
1420 return 0;
1421 }
1422