]> git.proxmox.com Git - libgit2.git/blame - src/refs.c
Brush up the refs API
[libgit2.git] / src / refs.c
CommitLineData
9282e921 1/*
2 * This file is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2,
4 * as published by the Free Software Foundation.
5 *
6 * In addition to the permissions in the GNU General Public License,
7 * the authors give you unlimited permission to link the compiled
8 * version of this file into combinations with other programs,
9 * and to distribute those combinations without any restriction
10 * coming from the use of this file. (The General Public License
11 * restrictions do apply in other respects; for example, they cover
12 * modification of the file, and distribution when not linked into
13 * a combined executable.)
14 *
15 * This file is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; see the file COPYING. If not, write to
22 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 */
25
26#include "refs.h"
27#include "hash.h"
28#include "repository.h"
29#include "fileops.h"
30
9282e921 31#define MAX_NESTING_LEVEL 5
32
32054c24
VM
33static int reference_write(git_reference *ref);
34
9282e921 35static const int default_table_size = 32;
36
fc658755 37static uint32_t reftable_hash(const void *key, int hash_id)
9282e921 38{
fc658755
VM
39 static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = {
40 2147483647,
41 0x5d20bb23,
42 0x7daaab3c
43 };
9282e921 44
fc658755 45 return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]);
9282e921 46}
47
2f8a8ab2 48static void reference_free(git_reference *reference)
9282e921 49{
2f8a8ab2
VM
50 if (reference == NULL)
51 return;
9282e921 52
2f8a8ab2
VM
53 if (reference->name)
54 free(reference->name);
9282e921 55
2f8a8ab2
VM
56 if (reference->type == GIT_REF_SYMBOLIC)
57 free(reference->target.ref);
9282e921 58
2f8a8ab2 59 free(reference);
9282e921 60}
61
32054c24 62static int reference_create(git_reference **ref_out, git_repository *repo, const char *name, git_rtype type) {
1d8cc731 63 char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
64 int error = GIT_SUCCESS;
9282e921 65 git_reference *reference = NULL;
66
1d8cc731 67 assert(ref_out && repo && name);
68
69 if (type != GIT_REF_SYMBOLIC && type != GIT_REF_OID)
70 return GIT_EMISSINGOBJDATA;
9282e921 71
2f8a8ab2 72 reference = git__malloc(sizeof(git_reference));
9282e921 73 if (reference == NULL)
74 return GIT_ENOMEM;
75
2f8a8ab2 76 memset(reference, 0x0, sizeof(git_reference));
2f8a8ab2 77 reference->owner = repo;
1d8cc731 78 reference->type = type;
79
80 error = git_reference__normalize_name(normalized, name, type);
81 if (error < GIT_SUCCESS)
82 goto cleanup;
83
84 reference->name = git__strdup(normalized);
85 if (reference->name == NULL) {
86 error = GIT_ENOMEM;
87 goto cleanup;
88 }
9282e921 89
2f8a8ab2 90 *ref_out = reference;
1d8cc731 91
92 return error;
93
94cleanup:
95 reference_free(reference);
96 return error;
97}
98
99int git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target)
100{
101 char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
102 int error = GIT_SUCCESS;
103 git_reference *ref = NULL;
104
32054c24 105 error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC);
1d8cc731 106 if (error < GIT_SUCCESS)
107 goto cleanup;
108
109 /* The target can aither be the name of an object id reference or the name of another symbolic reference */
110 error = git_reference__normalize_name(normalized, target, GIT_REF_ANY);
111 if (error < GIT_SUCCESS)
112 goto cleanup;
113
32054c24 114 /* set the target; this will write the reference on disk */
1d8cc731 115 error = git_reference_set_target(ref, normalized);
116 if (error < GIT_SUCCESS)
117 goto cleanup;
118
1d8cc731 119 *ref_out = ref;
120
121 return error;
122
123cleanup:
124 reference_free(ref);
125 return error;
126}
127
128int git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id)
129{
130 int error = GIT_SUCCESS;
131 git_reference *ref = NULL;
132
32054c24 133 error = reference_create(&ref, repo, name, GIT_REF_OID);
1d8cc731 134 if (error < GIT_SUCCESS)
135 goto cleanup;
136
32054c24 137 /* set the oid; this will write the reference on disk */
1d8cc731 138 error = git_reference_set_oid(ref, id);
139 if (error < GIT_SUCCESS)
140 goto cleanup;
141
1d8cc731 142 *ref_out = ref;
143
144 return error;
145
146cleanup:
147 reference_free(ref);
148 return error;
9282e921 149}
150
2f8a8ab2
VM
151static int parse_sym_ref(git_reference *ref, gitfo_buf *file_content)
152{
ddc9e79a 153 const unsigned int header_len = strlen(GIT_SYMREF);
2f8a8ab2
VM
154 const char *refname_start;
155 char *eol;
9282e921 156
2f8a8ab2 157 refname_start = (const char *)file_content->data;
9282e921 158
2f8a8ab2 159 if (file_content->len < (header_len + 1))
9282e921 160 return GIT_EREFCORRUPTED;
161
2f8a8ab2
VM
162 /*
163 * Assume we have already checked for the header
164 * before calling this function
165 */
9282e921 166
2f8a8ab2 167 refname_start += header_len;
9282e921 168
32054c24
VM
169 ref->target.ref = git__strdup(refname_start);
170 if (ref->target.ref == NULL)
171 return GIT_ENOMEM;
9282e921 172
2f8a8ab2
VM
173 /* remove newline at the end of file */
174 eol = strchr(ref->target.ref, '\n');
ff5873ad
VM
175 if (eol == NULL)
176 return GIT_EREFCORRUPTED;
177
178 *eol = '\0';
179 if (eol[-1] == '\r')
180 eol[-1] = '\0';
9282e921 181
2f8a8ab2 182 return GIT_SUCCESS;
9282e921 183}
184
2f8a8ab2
VM
185static int parse_oid_ref(git_reference *ref, gitfo_buf *file_content)
186{
187 char *buffer;
1d8cc731 188 git_oid id;
2f8a8ab2 189 buffer = (char *)file_content->data;
9282e921 190
2f8a8ab2 191 /* File format: 40 chars (OID) + newline */
ff5873ad 192 if (file_content->len < GIT_OID_HEXSZ + 1)
2f8a8ab2 193 return GIT_EREFCORRUPTED;
9282e921 194
1d8cc731 195 if (git_oid_mkstr(&id, buffer) < GIT_SUCCESS)
2f8a8ab2 196 return GIT_EREFCORRUPTED;
9282e921 197
32054c24 198 git_oid_cpy(&ref->target.oid, &id);
1d8cc731 199
ff5873ad
VM
200 buffer = buffer + GIT_OID_HEXSZ;
201 if (*buffer == '\r')
202 buffer++;
203
204 if (*buffer != '\n')
2f8a8ab2 205 return GIT_EREFCORRUPTED;
9282e921 206
2f8a8ab2 207 return GIT_SUCCESS;
9282e921 208}
209
2f8a8ab2 210static int read_loose_ref(gitfo_buf *file_content, const char *name, const char *repo_path)
9282e921 211{
212 int error = GIT_SUCCESS;
213 char ref_path[GIT_PATH_MAX];
214
215 /* Determine the full path of the ref */
995f9c34 216 git__joinpath(ref_path, repo_path, name);
9282e921 217
218 /* Does it even exist ? */
219 if (gitfo_exists(ref_path) < GIT_SUCCESS)
220 return GIT_ENOTFOUND;
221
222 /* A ref can not be a directory */
223 if (!gitfo_isdir(ref_path))
2f8a8ab2 224 return GIT_ENOTFOUND;
9282e921 225
2f8a8ab2
VM
226 if (file_content != NULL)
227 error = gitfo_read_file(file_content, ref_path);
9282e921 228
229 return error;
230}
231
2f8a8ab2
VM
232static int lookup_loose_ref(
233 git_reference **ref_out,
234 git_repository *repo,
235 const char *name)
9282e921 236{
237 int error = GIT_SUCCESS;
2f8a8ab2
VM
238 gitfo_buf ref_file = GITFO_BUF_INIT;
239 git_reference *ref = NULL;
9282e921 240
2f8a8ab2 241 *ref_out = NULL;
9282e921 242
2f8a8ab2 243 error = read_loose_ref(&ref_file, name, repo->path_repository);
9282e921 244 if (error < GIT_SUCCESS)
245 goto cleanup;
246
1d8cc731 247 if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) {
32054c24 248 error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC);
1d8cc731 249 if (error < GIT_SUCCESS)
250 goto cleanup;
9282e921 251
2f8a8ab2 252 error = parse_sym_ref(ref, &ref_file);
1d8cc731 253 } else {
32054c24 254 error = reference_create(&ref, repo, name, GIT_REF_OID);
1d8cc731 255 if (error < GIT_SUCCESS)
256 goto cleanup;
257
2f8a8ab2 258 error = parse_oid_ref(ref, &ref_file);
1d8cc731 259 }
260
2f8a8ab2 261 if (error < GIT_SUCCESS)
9282e921 262 goto cleanup;
9282e921 263
ff5873ad
VM
264 error = git_hashtable_insert(repo->references.cache, ref->name, ref);
265 if (error < GIT_SUCCESS)
266 goto cleanup;
267
2f8a8ab2
VM
268 *ref_out = ref;
269 return GIT_SUCCESS;
9282e921 270
271cleanup:
2f8a8ab2
VM
272 gitfo_free_buf(&ref_file);
273 reference_free(ref);
9282e921 274 return error;
275}
276
2f8a8ab2
VM
277
278static int read_packed_refs(gitfo_buf *packfile, const char *repo_path)
9282e921 279{
9282e921 280 char ref_path[GIT_PATH_MAX];
281
282 /* Determine the full path of the file */
995f9c34 283 git__joinpath(ref_path, repo_path, GIT_PACKEDREFS_FILE);
9282e921 284
285 /* Does it even exist ? */
286 if (gitfo_exists(ref_path) < GIT_SUCCESS)
287 return GIT_ENOTFOUND;
288
2f8a8ab2 289 return gitfo_read_file(packfile, ref_path);
9282e921 290}
291
2f8a8ab2
VM
292static int parse_packed_line_peel(
293 git_reference **ref_out,
294 const git_reference *tag_ref,
295 const char **buffer_out,
296 const char *buffer_end)
9282e921 297{
2f8a8ab2
VM
298 git_oid oid;
299 const char *buffer = *buffer_out + 1;
300
301 assert(buffer[-1] == '^');
9282e921 302
303 /* Ensure it's not the first entry of the file */
2f8a8ab2 304 if (tag_ref == NULL)
9282e921 305 return GIT_EPACKEDREFSCORRUPTED;
306
307 /* Ensure reference is a tag */
2f8a8ab2 308 if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0)
9282e921 309 return GIT_EPACKEDREFSCORRUPTED;
310
2f8a8ab2
VM
311 if (buffer + GIT_OID_HEXSZ >= buffer_end)
312 return GIT_EPACKEDREFSCORRUPTED;
9282e921 313
2f8a8ab2
VM
314 /* Is this a valid object id? */
315 if (git_oid_mkstr(&oid, buffer) < GIT_SUCCESS)
316 return GIT_EPACKEDREFSCORRUPTED;
317
ff5873ad
VM
318 buffer = buffer + GIT_OID_HEXSZ;
319 if (*buffer == '\r')
320 buffer++;
321
322 if (*buffer != '\n')
323 return GIT_EPACKEDREFSCORRUPTED;
324
325 *buffer_out = buffer + 1;
2f8a8ab2
VM
326
327 /*
328 * TODO: do we need the packed line?
329 * Right now we don't, so we don't create a new
330 * reference.
331 */
332
333 *ref_out = NULL;
334 return GIT_SUCCESS;
9282e921 335}
336
2f8a8ab2
VM
337static int parse_packed_line(
338 git_reference **ref_out,
339 git_repository *repo,
340 const char **buffer_out,
341 const char *buffer_end)
9282e921 342{
2f8a8ab2
VM
343 git_reference *ref;
344
345 const char *buffer = *buffer_out;
346 const char *refname_begin, *refname_end;
347
9282e921 348 int error = GIT_SUCCESS;
9282e921 349 int refname_len;
1d8cc731 350 char refname[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
351 git_oid id;
9282e921 352
2f8a8ab2
VM
353 refname_begin = (buffer + GIT_OID_HEXSZ + 1);
354 if (refname_begin >= buffer_end ||
355 refname_begin[-1] != ' ') {
356 error = GIT_EPACKEDREFSCORRUPTED;
357 goto cleanup;
358 }
9282e921 359
2f8a8ab2 360 /* Is this a valid object id? */
1d8cc731 361 if ((error = git_oid_mkstr(&id, buffer)) < GIT_SUCCESS)
2f8a8ab2 362 goto cleanup;
9282e921 363
2f8a8ab2
VM
364 refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
365 if (refname_end == NULL) {
366 error = GIT_EPACKEDREFSCORRUPTED;
367 goto cleanup;
368 }
9282e921 369
2f8a8ab2 370 refname_len = refname_end - refname_begin;
9282e921 371
1d8cc731 372 memcpy(refname, refname_begin, refname_len);
373 refname[refname_len] = 0;
9282e921 374
1d8cc731 375 if (refname[refname_len - 1] == '\r')
376 refname[refname_len - 1] = 0;
9282e921 377
32054c24 378 error = reference_create(&ref, repo, refname, GIT_REF_OID);
1d8cc731 379 if (error < GIT_SUCCESS)
380 goto cleanup;
381
32054c24 382 git_oid_cpy(&ref->target.oid, &id);
ddc9e79a 383
2f8a8ab2 384 ref->packed = 1;
9282e921 385
2f8a8ab2 386 *ref_out = ref;
9282e921 387 *buffer_out = refname_end + 1;
388
2f8a8ab2
VM
389 return GIT_SUCCESS;
390
391cleanup:
392 reference_free(ref);
9282e921 393 return error;
394}
395
2f8a8ab2 396static int parse_packed_refs(git_refcache *ref_cache, git_repository *repo)
9282e921 397{
398 int error = GIT_SUCCESS;
2f8a8ab2
VM
399 gitfo_buf packfile = GITFO_BUF_INIT;
400 const char *buffer_start, *buffer_end;
401
402 error = read_packed_refs(&packfile, repo->path_repository);
9282e921 403 if (error < GIT_SUCCESS)
404 goto cleanup;
405
2f8a8ab2
VM
406 buffer_start = (const char *)packfile.data;
407 buffer_end = (const char *)(buffer_start) + packfile.len;
9282e921 408
2f8a8ab2 409 /* Does the header look like valid? */
9282e921 410 if (git__prefixcmp((const char *)(buffer_start), GIT_PACKEDREFS_HEADER)) {
411 error = GIT_EPACKEDREFSCORRUPTED;
412 goto cleanup;
413 }
414
415 /* Let's skip the header */
416 buffer_start += strlen(GIT_PACKEDREFS_HEADER);
417
ddc9e79a
VM
418 if (*buffer_start == '\r')
419 buffer_start++;
420
421 if (*buffer_start != '\n')
422 return GIT_EPACKEDREFSCORRUPTED;
423
424 buffer_start++;
425
9282e921 426 while (buffer_start < buffer_end) {
2f8a8ab2
VM
427
428 git_reference *ref = NULL;
429 git_reference *ref_tag = NULL;
430
431 error = parse_packed_line(&ref, repo, &buffer_start, buffer_end);
432 if (error < GIT_SUCCESS)
433 goto cleanup;
434
9282e921 435 if (buffer_start[0] == '^') {
2f8a8ab2 436 error = parse_packed_line_peel(&ref_tag, ref, &buffer_start, buffer_end);
9282e921 437 if (error < GIT_SUCCESS)
438 goto cleanup;
2f8a8ab2 439 }
9282e921 440
2f8a8ab2
VM
441 /*
442 * If a loose reference exists with the same name,
443 * we assume that the loose reference is more up-to-date.
444 * We don't need to cache this ref from the packfile.
445 */
446 if (read_loose_ref(NULL, ref->name, repo->path_repository) == GIT_SUCCESS) {
447 reference_free(ref);
448 reference_free(ref_tag);
9282e921 449 continue;
450 }
451
2f8a8ab2
VM
452 error = git_hashtable_insert(ref_cache->cache, ref->name, ref);
453 if (error < GIT_SUCCESS) {
454 reference_free(ref);
455 reference_free(ref_tag);
9282e921 456 goto cleanup;
2f8a8ab2
VM
457 }
458 }
9282e921 459
2f8a8ab2 460 ref_cache->pack_loaded = 1;
9282e921 461
2f8a8ab2
VM
462cleanup:
463 gitfo_free_buf(&packfile);
464 return error;
465}
9282e921 466
1d8cc731 467int git_reference_set_oid(git_reference *ref, const git_oid *id)
2f8a8ab2 468{
1d8cc731 469 if (ref->type != GIT_REF_OID)
470 return GIT_EINVALIDREFSTATE;
9282e921 471
2f8a8ab2 472 git_oid_cpy(&ref->target.oid, id);
9282e921 473
32054c24 474 return reference_write(ref);
2f8a8ab2
VM
475}
476
1d8cc731 477int git_reference_set_target(git_reference *ref, const char *target)
2f8a8ab2 478{
1d8cc731 479 if (ref->type != GIT_REF_SYMBOLIC)
480 return GIT_EINVALIDREFSTATE;
9282e921 481
1d8cc731 482 free(ref->target.ref);
2f8a8ab2 483 ref->target.ref = git__strdup(target);
1d8cc731 484 if (ref->target.ref == NULL)
485 return GIT_ENOMEM;
9282e921 486
32054c24 487 return reference_write(ref);
2f8a8ab2 488}
9282e921 489
2f8a8ab2
VM
490const git_oid *git_reference_oid(git_reference *ref)
491{
492 assert(ref);
493
494 if (ref->type != GIT_REF_OID)
495 return NULL;
496
497 return &ref->target.oid;
9282e921 498}
499
2f8a8ab2 500const char *git_reference_target(git_reference *ref)
9282e921 501{
32054c24
VM
502 assert(ref);
503
2f8a8ab2
VM
504 if (ref->type != GIT_REF_SYMBOLIC)
505 return NULL;
9282e921 506
2f8a8ab2
VM
507 return ref->target.ref;
508}
9282e921 509
2f8a8ab2
VM
510git_rtype git_reference_type(git_reference *ref)
511{
512 assert(ref);
513 return ref->type;
514}
9282e921 515
2f8a8ab2
VM
516const char *git_reference_name(git_reference *ref)
517{
518 assert(ref);
519 return ref->name;
520}
9282e921 521
2f8a8ab2
VM
522git_repository *git_reference_owner(git_reference *ref)
523{
524 assert(ref);
525 return ref->owner;
526}
527
528int git_reference_resolve(git_reference **resolved_ref, git_reference *ref)
529{
530 git_repository *repo;
531 int error, i;
532
533 assert(resolved_ref && ref);
534 *resolved_ref = NULL;
535
536 repo = ref->owner;
537
538 for (i = 0; i < MAX_NESTING_LEVEL; ++i) {
9282e921 539
2f8a8ab2
VM
540 if (ref->type == GIT_REF_OID) {
541 *resolved_ref = ref;
9282e921 542 return GIT_SUCCESS;
543 }
9282e921 544
2f8a8ab2
VM
545 if ((error = git_repository_lookup_ref(&ref, repo, ref->target.ref)) < GIT_SUCCESS)
546 return error;
9282e921 547 }
548
2f8a8ab2
VM
549 return GIT_ETOONESTEDSYMREF;
550}
9282e921 551
32054c24 552static int reference_write(git_reference *ref)
2f8a8ab2 553{
817c2820 554 git_filebuf file;
2f8a8ab2
VM
555 char ref_path[GIT_PATH_MAX];
556 int error, contents_size;
557 char *ref_contents = NULL;
9282e921 558
32054c24 559 assert(ref->type == GIT_REF_OID || ref->type == GIT_REF_SYMBOLIC);
9282e921 560
995f9c34 561 git__joinpath(ref_path, ref->owner->path_repository, ref->name);
9282e921 562
817c2820 563 if ((error = git_filebuf_open(&file, ref_path, 0)) < GIT_SUCCESS)
2f8a8ab2
VM
564 goto error_cleanup;
565
566 if (ref->type == GIT_REF_OID) {
567
568 contents_size = GIT_OID_HEXSZ + 1;
569 ref_contents = git__malloc(contents_size);
570 if (ref_contents == NULL) {
571 error = GIT_ENOMEM;
1d8cc731 572 goto unlock;
2f8a8ab2
VM
573 }
574
575 git_oid_fmt(ref_contents, &ref->target.oid);
2f8a8ab2
VM
576
577 } else { /* GIT_REF_SYMBOLIC */
578
579 contents_size = strlen(GIT_SYMREF) + strlen(ref->target.ref) + 1;
580 ref_contents = git__malloc(contents_size);
581 if (ref_contents == NULL) {
582 error = GIT_ENOMEM;
1d8cc731 583 goto unlock;
2f8a8ab2
VM
584 }
585
586 strcpy(ref_contents, GIT_SYMREF);
587 strcat(ref_contents, ref->target.ref);
9282e921 588 }
589
32054c24 590 /* TODO: win32 carriage return when writing references in Windows? */
1d8cc731 591 ref_contents[contents_size - 1] = '\n';
592
817c2820 593 if ((error = git_filebuf_write(&file, ref_contents, contents_size)) < GIT_SUCCESS)
2f8a8ab2
VM
594 goto error_cleanup;
595
817c2820 596 error = git_filebuf_commit(&file);
32054c24
VM
597 if (error < GIT_SUCCESS)
598 goto unlock;
2f8a8ab2 599
32054c24
VM
600 error = git_hashtable_insert(ref->owner->references.cache, ref->name, ref);
601 if (error < GIT_SUCCESS)
602 goto unlock;
2f8a8ab2 603
32054c24
VM
604 free(ref_contents);
605 return GIT_SUCCESS;
1d8cc731 606
607unlock:
32054c24 608 git_filebuf_cleanup(&lock);
2f8a8ab2 609 free(ref_contents);
2f8a8ab2
VM
610 return error;
611}
612
613int git_repository_lookup_ref(git_reference **ref_out, git_repository *repo, const char *name)
614{
615 int error;
aa2120e9 616 char normalized_name[GIT_PATH_MAX];
2f8a8ab2
VM
617
618 assert(ref_out && repo && name);
619
620 *ref_out = NULL;
621
aa2120e9 622 error = git_reference__normalize_name(normalized_name, name, GIT_REF_ANY);
2f8a8ab2
VM
623 if (error < GIT_SUCCESS)
624 return error;
625
626 /*
627 * First, check if the reference is on the local cache;
628 * references on the cache are assured to be up-to-date
629 */
aa2120e9 630 *ref_out = git_hashtable_lookup(repo->references.cache, normalized_name);
2f8a8ab2
VM
631 if (*ref_out != NULL)
632 return GIT_SUCCESS;
633
634 /*
635 * Then check if there is a loose file for that reference.
636 * If the file exists, we parse it and store it on the
637 * cache.
638 */
aa2120e9 639 error = lookup_loose_ref(ref_out, repo, normalized_name);
2f8a8ab2
VM
640
641 if (error == GIT_SUCCESS)
642 return GIT_SUCCESS;
643
9282e921 644 if (error != GIT_ENOTFOUND)
2f8a8ab2 645 return error;
9282e921 646
2f8a8ab2
VM
647 /*
648 * Check if we have loaded the packed references.
649 * If the packed references have been loaded, they would be
650 * stored already on the cache: that means that the ref
651 * we are looking for doesn't exist.
652 *
653 * If they haven't been loaded yet, we load the packfile
654 * and check if our reference is inside of it.
655 */
656 if (!repo->references.pack_loaded) {
657
658 /* load all the packed references */
659 error = parse_packed_refs(&repo->references, repo);
660 if (error < GIT_SUCCESS)
661 return error;
9282e921 662
2f8a8ab2 663 /* check the cache again -- hopefully the reference will be there */
aa2120e9 664 *ref_out = git_hashtable_lookup(repo->references.cache, normalized_name);
2f8a8ab2
VM
665 if (*ref_out != NULL)
666 return GIT_SUCCESS;
9282e921 667 }
668
2f8a8ab2
VM
669 /* The reference doesn't exist anywhere */
670 return GIT_ENOTFOUND;
671}
9282e921 672
2f8a8ab2
VM
673int git_repository__refcache_init(git_refcache *refs)
674{
675 assert(refs);
9282e921 676
2f8a8ab2
VM
677 refs->cache = git_hashtable_alloc(
678 default_table_size,
679 reftable_hash,
fc658755 680 (git_hash_keyeq_ptr)strcmp);
9282e921 681
2f8a8ab2
VM
682 return refs->cache ? GIT_SUCCESS : GIT_ENOMEM;
683}
9282e921 684
2f8a8ab2
VM
685void git_repository__refcache_free(git_refcache *refs)
686{
fc658755 687 const char *ref_name;
2f8a8ab2 688 git_reference *reference;
9282e921 689
2f8a8ab2
VM
690 assert(refs);
691
fc658755
VM
692 GIT_HASHTABLE_FOREACH(refs->cache, ref_name, reference,
693 reference_free(reference)
694 );
2f8a8ab2
VM
695
696 git_hashtable_free(refs->cache);
9282e921 697}
2f8a8ab2 698
aa2120e9 699static int check_valid_ref_char(char ch)
700{
701 if (ch <= ' ')
702 return GIT_ERROR;
703
704 switch (ch) {
705 case '~':
706 case '^':
707 case ':':
708 case '\\':
709 case '?':
710 case '[':
e1be1028 711 case '*':
aa2120e9 712 return GIT_ERROR;
713 break;
714
715 default:
716 return GIT_SUCCESS;
717 }
718}
719
720int git_reference__normalize_name(char *buffer_out, const char *name, git_rtype type)
721{
722 int error = GIT_SUCCESS;
723 const char *name_end, *buffer_out_start;
724 char *current;
725 int contains_a_slash = 0;
726
727 assert(name && buffer_out);
728
729 buffer_out_start = buffer_out;
730 current = (char *)name;
731 name_end = name + strlen(name);
732
733 if (type == GIT_REF_INVALID)
734 return GIT_EINVALIDTYPE;
735
736 /* A refname can not be empty */
737 if (name_end == name)
738 return GIT_EINVALIDREFNAME;
739
740 /* A refname can not end with a dot or a slash */
741 if (*(name_end - 1) == '.' || *(name_end - 1) == '/')
742 return GIT_EINVALIDREFNAME;
743
744 while (current < name_end) {
745 if (check_valid_ref_char(*current))
746 return GIT_EINVALIDREFNAME;
747
748 if (buffer_out > buffer_out_start) {
749 char prev = *(buffer_out - 1);
750
751 /* A refname can not start with a dot nor contain a double dot */
752 if (*current == '.' && ((prev == '.') || (prev == '/')))
753 return GIT_EINVALIDREFNAME;
754
755 /* '@{' is forbidden within a refname */
756 if (*current == '{' && prev == '@')
757 return GIT_EINVALIDREFNAME;
758
759 /* Prevent multiple slashes from being added to the output */
760 if (*current == '/' && prev == '/') {
761 current++;
762 continue;
763 }
764 }
765
e1be1028 766 if (*current == '/')
aa2120e9 767 contains_a_slash = 1;
aa2120e9 768
769 *buffer_out++ = *current++;
770 }
771
772 /* Object id refname have to contain at least one slash */
773 if (type == GIT_REF_OID && !contains_a_slash)
774 return GIT_EINVALIDREFNAME;
775
776 /* A refname can not end with ".lock" */
777 if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION))
778 return GIT_EINVALIDREFNAME;
779
780 *buffer_out = '\0';
781
782 /* For object id references, name has to start with refs/(heads|tags|remotes) */
783 if (type == GIT_REF_OID && !(!git__prefixcmp(buffer_out_start, GIT_REFS_HEADS_DIR) ||
784 !git__prefixcmp(buffer_out_start, GIT_REFS_TAGS_DIR) || !git__prefixcmp(buffer_out_start, GIT_REFS_REMOTES_DIR)))
785 return GIT_EINVALIDREFNAME;
786
787 return error;
788}
2f8a8ab2 789