refs.c 47.7 KB
Newer Older
1
/*
Vicent Marti committed
2
 * Copyright (C) 2009-2011 the libgit2 contributors
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6 7 8 9 10 11 12
 */

#include "refs.h"
#include "hash.h"
#include "repository.h"
#include "fileops.h"

Vicent Marti committed
13 14 15
#include <git2/tag.h>
#include <git2/object.h>

16 17
#define MAX_NESTING_LEVEL 5

18 19 20 21 22 23 24 25 26 27 28
typedef struct {
	git_reference ref;
	git_oid oid;
	git_oid peel_target;
} reference_oid;

typedef struct {
	git_reference ref;
	char *target;
} reference_symbolic;

29 30
static const int default_table_size = 32;

31
static uint32_t reftable_hash(const void *key, int hash_id)
32
{
33 34 35
	static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = {
		2147483647,
		0x5d20bb23,
36
		0x7daaab3c
37
	};
38

39
	return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]);
40 41
}

Vicent Marti committed
42 43
static void reference_free(git_reference *reference);
static int reference_create(git_reference **ref_out, git_repository *repo, const char *name, git_rtype type);
44
static int reference_read(git_fbuffer *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated);
Vicent Marti committed
45 46

/* loose refs */
Vicent Marti committed
47 48
static int loose_parse_symbolic(git_reference *ref, git_fbuffer *file_content);
static int loose_parse_oid(git_reference *ref, git_fbuffer *file_content);
49
static int loose_lookup(git_reference **ref_out, git_repository *repo, const char *name, int skip_symbolic);
Vicent Marti committed
50
static int loose_write(git_reference *ref);
51
static int loose_update(git_reference *ref);
Vicent Marti committed
52 53 54 55 56 57 58 59 60 61 62 63

/* packed refs */
static int packed_parse_peel(reference_oid *tag_ref, const char **buffer_out, const char *buffer_end);
static int packed_parse_oid(reference_oid **ref_out, git_repository *repo, const char **buffer_out, const char *buffer_end);
static int packed_load(git_repository *repo);
static int packed_loadloose(git_repository *repository);
static int packed_write_ref(reference_oid *ref, git_filebuf *file);
static int packed_find_peel(reference_oid *ref);
static int packed_remove_loose(git_repository *repo, git_vector *packing_list);
static int packed_sort(const void *a, const void *b);
static int packed_write(git_repository *repo);

64
/* internal helpers */
65
static int reference_available(git_repository *repo, const char *ref, const char *old_ref);
66

Vicent Marti committed
67 68
/* name normalization */
static int check_valid_ref_char(char ch);
69
static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref);
Vicent Marti committed
70 71 72 73

/*****************************************
 * Internal methods - Constructor/destructor
 *****************************************/
74
static void reference_free(git_reference *reference)
75
{
76 77
	if (reference == NULL)
		return;
78

79 80
	if (reference->name)
		free(reference->name);
81

82
	if (reference->type == GIT_REF_SYMBOLIC)
83
		free(((reference_symbolic *)reference)->target);
84

85
	free(reference);
86 87
}

Vicent Marti committed
88 89 90 91 92 93
static int reference_create(
	git_reference **ref_out,
	git_repository *repo,
	const char *name,
	git_rtype type)
{
94
	char normalized[GIT_REFNAME_MAX];
95
	int error = GIT_SUCCESS, size;
96 97
	git_reference *reference = NULL;

98 99
	assert(ref_out && repo && name);

100 101 102 103 104
	if (type == GIT_REF_SYMBOLIC)
		size = sizeof(reference_symbolic);
	else if (type == GIT_REF_OID)
		size = sizeof(reference_oid);
	else
105 106
		return git__throw(GIT_EINVALIDARGS,
			"Invalid reference type. Use either GIT_REF_OID or GIT_REF_SYMBOLIC as type specifier");
107

108
	reference = git__malloc(size);
109 110 111
	if (reference == NULL)
		return GIT_ENOMEM;

112
	memset(reference, 0x0, size);
113
	reference->owner = repo;
114 115
	reference->type = type;

116
	error = normalize_name(normalized, sizeof(normalized), name, (type & GIT_REF_OID));
117 118 119 120 121 122 123 124
	if (error < GIT_SUCCESS)
		goto cleanup;

	reference->name = git__strdup(normalized);
	if (reference->name == NULL) {
		error = GIT_ENOMEM;
		goto cleanup;
	}
125

126
	*ref_out = reference;
127

128
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference");
129 130 131

cleanup:
	reference_free(reference);
132
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference");
133 134
}

135
static int reference_read(git_fbuffer *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated)
136 137 138
{
	char path[GIT_PATH_MAX];

139 140
	assert(file_content && repo_path && ref_name);

141
	/* Determine the full path of the file */
Vicent Marti committed
142
	git_path_join(path, repo_path, ref_name);
143

144
	return git_futils_readbuffer_updated(file_content, path, mtime, updated);
145 146
}

147 148


149

Vicent Marti committed
150 151 152
/*****************************************
 * Internal methods - Loose references
 *****************************************/
153 154
static int loose_update(git_reference *ref)
{
155
	int error, updated;
Vicent Marti committed
156
	git_fbuffer ref_file = GIT_FBUFFER_INIT;
157 158 159 160

	if (ref->type & GIT_REF_PACKED)
		return packed_load(ref->owner);

161
/*	error = reference_read(NULL, &ref_time, ref->owner->path_repository, ref->name);
162 163 164 165 166
	if (error < GIT_SUCCESS)
		goto cleanup;

	if (ref_time == ref->mtime)
		return GIT_SUCCESS;
167 168
*/
	error = reference_read(&ref_file, &ref->mtime, ref->owner->path_repository, ref->name, &updated);
169 170 171
	if (error < GIT_SUCCESS)
		goto cleanup;

172 173 174
	if (!updated)
		goto cleanup;

175 176 177 178 179
	if (ref->type == GIT_REF_SYMBOLIC)
		error = loose_parse_symbolic(ref, &ref_file);
	else if (ref->type == GIT_REF_OID)
		error = loose_parse_oid(ref, &ref_file);
	else
180 181
		error = git__throw(GIT_EOBJCORRUPTED,
			"Invalid reference type (%d) for loose reference", ref->type);
182 183 184


cleanup:
185
	git_futils_freebuffer(&ref_file);
186 187 188 189 190
	if (error != GIT_SUCCESS) {
		reference_free(ref);
		git_hashtable_remove(ref->owner->references.loose_cache, ref->name);
	}

191
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to update loose reference");
192 193
}

Vicent Marti committed
194
static int loose_parse_symbolic(git_reference *ref, git_fbuffer *file_content)
195
{
196
	const unsigned int header_len = strlen(GIT_SYMREF);
197 198
	const char *refname_start;
	char *eol;
199
	reference_symbolic *ref_sym;
200

201
	refname_start = (const char *)file_content->data;
202
	ref_sym = (reference_symbolic *)ref;
203

204
	if (file_content->len < (header_len + 1))
205 206
		return git__throw(GIT_EOBJCORRUPTED,
			"Failed to parse loose reference. Object too short");
207

208
	/*
209
	 * Assume we have already checked for the header
210
	 * before calling this function
211
	 */
212

213
	refname_start += header_len;
214

215
	free(ref_sym->target);
216 217
	ref_sym->target = git__strdup(refname_start);
	if (ref_sym->target == NULL)
Vicent Marti committed
218
		return GIT_ENOMEM;
219

220
	/* remove newline at the end of file */
221
	eol = strchr(ref_sym->target, '\n');
222
	if (eol == NULL)
223 224
		return git__throw(GIT_EOBJCORRUPTED,
			"Failed to parse loose reference. Missing EOL");
225 226 227 228

	*eol = '\0';
	if (eol[-1] == '\r')
		eol[-1] = '\0';
229

230
	return GIT_SUCCESS;
231 232
}

Vicent Marti committed
233
static int loose_parse_oid(git_reference *ref, git_fbuffer *file_content)
234
{
235
	int error;
236
	reference_oid *ref_oid;
237
	char *buffer;
238

239
	buffer = (char *)file_content->data;
240
	ref_oid = (reference_oid *)ref;
241

242
	/* File format: 40 chars (OID) + newline */
243
	if (file_content->len < GIT_OID_HEXSZ + 1)
244 245
		return git__throw(GIT_EOBJCORRUPTED,
			"Failed to parse loose reference. Reference too short");
246

Vicent Marti committed
247
	if ((error = git_oid_fromstr(&ref_oid->oid, buffer)) < GIT_SUCCESS)
248
		return git__rethrow(GIT_EOBJCORRUPTED, "Failed to parse loose reference.");
249

250 251 252 253 254
	buffer = buffer + GIT_OID_HEXSZ;
	if (*buffer == '\r')
		buffer++;

	if (*buffer != '\n')
255 256
		return git__throw(GIT_EOBJCORRUPTED,
			"Failed to parse loose reference. Missing EOL");
257

258
	return GIT_SUCCESS;
259 260 261
}


262 263
static git_rtype loose_guess_rtype(const char *full_path)
{
Vicent Marti committed
264
	git_fbuffer ref_file = GIT_FBUFFER_INIT;
265 266 267 268
	git_rtype type;

	type = GIT_REF_INVALID;

Vicent Marti committed
269
	if (git_futils_readbuffer(&ref_file, full_path) == GIT_SUCCESS) {
270 271 272 273 274 275
		if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0)
			type = GIT_REF_SYMBOLIC;
		else
			type = GIT_REF_OID;
	}

Vicent Marti committed
276
	git_futils_freebuffer(&ref_file);
277 278 279
	return type;
}

Vicent Marti committed
280
static int loose_lookup(
281 282
		git_reference **ref_out,
		git_repository *repo,
Vicent Marti committed
283 284
		const char *name,
		int skip_symbolic)
285 286
{
	int error = GIT_SUCCESS;
Vicent Marti committed
287
	git_fbuffer ref_file = GIT_FBUFFER_INIT;
288
	git_reference *ref = NULL;
289
	time_t ref_time = 0;
290

291
	*ref_out = NULL;
292

293
	error = reference_read(&ref_file, &ref_time, repo->path_repository, name, NULL);
294 295 296
	if (error < GIT_SUCCESS)
		goto cleanup;

297
	if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) {
Vicent Marti committed
298 299 300
		if (skip_symbolic)
			return GIT_SUCCESS;

Vicent Marti committed
301
		error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC);
302 303
		if (error < GIT_SUCCESS)
			goto cleanup;
304

Vicent Marti committed
305
		error = loose_parse_symbolic(ref, &ref_file);
306
	} else {
Vicent Marti committed
307
		error = reference_create(&ref, repo, name, GIT_REF_OID);
308 309 310
		if (error < GIT_SUCCESS)
			goto cleanup;

Vicent Marti committed
311
		error = loose_parse_oid(ref, &ref_file);
312 313
	}

314
	if (error < GIT_SUCCESS)
315 316
		goto cleanup;

317
	ref->mtime = ref_time;
318
	*ref_out = ref;
Vicent Marti committed
319
	git_futils_freebuffer(&ref_file);
320
	return GIT_SUCCESS;
321 322

cleanup:
Vicent Marti committed
323
	git_futils_freebuffer(&ref_file);
324
	reference_free(ref);
325
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to lookup loose reference");
326 327
}

Vicent Marti committed
328 329 330 331
static int loose_write(git_reference *ref)
{
	git_filebuf file;
	char ref_path[GIT_PATH_MAX];
332
	int error;
333
	struct stat st;
Vicent Marti committed
334

Vicent Marti committed
335
	git_path_join(ref_path, ref->owner->path_repository, ref->name);
Vicent Marti committed
336

337
	if ((error = git_filebuf_open(&file, ref_path, GIT_FILEBUF_FORCE)) < GIT_SUCCESS)
338
		return git__rethrow(error, "Failed to write loose reference");
Vicent Marti committed
339 340 341

	if (ref->type & GIT_REF_OID) {
		reference_oid *ref_oid = (reference_oid *)ref;
342
		char oid[GIT_OID_HEXSZ + 1];
Vicent Marti committed
343

344
		memset(oid, 0x0, sizeof(oid));
Vicent Marti committed
345

346 347 348 349
		git_oid_fmt(oid, &ref_oid->oid);
		error = git_filebuf_printf(&file, "%s\n", oid);
		if (error < GIT_SUCCESS)
			goto unlock;
Vicent Marti committed
350 351 352 353

	} else if (ref->type & GIT_REF_SYMBOLIC) { /* GIT_REF_SYMBOLIC */
		reference_symbolic *ref_sym = (reference_symbolic *)ref;

354
		error = git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref_sym->target);
Vicent Marti committed
355
	} else {
356
		error = git__throw(GIT_EOBJCORRUPTED, "Failed to write reference. Invalid reference type");
Vicent Marti committed
357 358 359 360 361
		goto unlock;
	}

	error = git_filebuf_commit(&file);

Vicent Marti committed
362
	if (p_stat(ref_path, &st) == GIT_SUCCESS)
363 364
		ref->mtime = st.st_mtime;

365
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference");
Vicent Marti committed
366 367 368

unlock:
	git_filebuf_cleanup(&file);
369
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference");
Vicent Marti committed
370 371 372 373 374 375
}





376

Vicent Marti committed
377 378 379
/*****************************************
 * Internal methods - Packed references
 *****************************************/
380

Vicent Marti committed
381
static int packed_parse_peel(
382
		reference_oid *tag_ref,
383
		const char **buffer_out,
384
		const char *buffer_end)
385
{
386 387 388
	const char *buffer = *buffer_out + 1;

	assert(buffer[-1] == '^');
389 390

	/* Ensure it's not the first entry of the file */
391
	if (tag_ref == NULL)
392
		return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is the first entry of the file");
393 394

	/* Ensure reference is a tag */
395
	if (git__prefixcmp(tag_ref->ref.name, GIT_REFS_TAGS_DIR) != 0)
396
		return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is not a tag");
397

398
	if (buffer + GIT_OID_HEXSZ >= buffer_end)
399
		return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer too small");
400

401
	/* Is this a valid object id? */
Vicent Marti committed
402
	if (git_oid_fromstr(&tag_ref->peel_target, buffer) < GIT_SUCCESS)
403
		return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Not a valid object ID");
404

405 406 407 408 409
	buffer = buffer + GIT_OID_HEXSZ;
	if (*buffer == '\r')
		buffer++;

	if (*buffer != '\n')
410
		return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer not terminated correctly");
411 412

	*buffer_out = buffer + 1;
413
	tag_ref->ref.type |= GIT_REF_HAS_PEEL;
414 415

	return GIT_SUCCESS;
416 417
}

Vicent Marti committed
418
static int packed_parse_oid(
419
		reference_oid **ref_out,
420 421 422
		git_repository *repo,
		const char **buffer_out,
		const char *buffer_end)
423
{
424
	git_reference *_ref = NULL;
425
	reference_oid *ref = NULL;
426 427 428 429

	const char *buffer = *buffer_out;
	const char *refname_begin, *refname_end;

430 431
	int error = GIT_SUCCESS;
	int refname_len;
432
	char refname[GIT_REFNAME_MAX];
433
	git_oid id;
434

435 436 437 438 439 440
	refname_begin = (buffer + GIT_OID_HEXSZ + 1);
	if (refname_begin >= buffer_end ||
		refname_begin[-1] != ' ') {
		error = GIT_EPACKEDREFSCORRUPTED;
		goto cleanup;
	}
441

442
	/* Is this a valid object id? */
Vicent Marti committed
443
	if ((error = git_oid_fromstr(&id, buffer)) < GIT_SUCCESS)
444
		goto cleanup;
445

446 447 448 449 450
	refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
	if (refname_end == NULL) {
		error = GIT_EPACKEDREFSCORRUPTED;
		goto cleanup;
	}
451

452
	refname_len = refname_end - refname_begin;
453

454 455
	memcpy(refname, refname_begin, refname_len);
	refname[refname_len] = 0;
456

457 458
	if (refname[refname_len - 1] == '\r')
		refname[refname_len - 1] = 0;
459

460
	error = reference_create(&_ref, repo, refname, GIT_REF_OID);
461 462 463
	if (error < GIT_SUCCESS)
		goto cleanup;

464 465
	ref = (reference_oid *)_ref;

466 467
	git_oid_cpy(&ref->oid, &id);
	ref->ref.type |= GIT_REF_PACKED;
468

469
	*ref_out = ref;
470 471
	*buffer_out = refname_end + 1;

472 473 474
	return GIT_SUCCESS;

cleanup:
475
	reference_free((git_reference *)ref);
476
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse OID of packed reference");
477 478
}

Vicent Marti committed
479
static int packed_load(git_repository *repo)
480
{
481
	int error = GIT_SUCCESS, updated;
Vicent Marti committed
482
	git_fbuffer packfile = GIT_FBUFFER_INIT;
483
	const char *buffer_start, *buffer_end;
Vicent Marti committed
484 485
	git_refcache *ref_cache = &repo->references;

486 487
	/* First we make sure we have allocated the hash table */
	if (ref_cache->packfile == NULL) {
488
		ref_cache->packfile = git_hashtable_alloc(
489
			default_table_size,
490
			reftable_hash,
491
			(git_hash_keyeq_ptr)(&git__strcmp_cb));
492

493 494 495 496
		if (ref_cache->packfile == NULL) {
			error = GIT_ENOMEM;
			goto cleanup;
		}
497 498
	}

499
	error = reference_read(&packfile, &ref_cache->packfile_time,
Vicent Marti committed
500
							repo->path_repository, GIT_PACKEDREFS_FILE, &updated);
Vicent Marti committed
501

502 503 504 505 506 507 508 509 510
	/*
	 * If we couldn't find the file, we need to clear the table and
	 * return. On any other error, we return that error. If everything
	 * went fine and the file wasn't updated, then there's nothing new
	 * for us here, so just return. Anything else means we need to
	 * refresh the packed refs.
	 */
	if (error == GIT_ENOTFOUND) {
		git_hashtable_clear(ref_cache->packfile);
Vicent Marti committed
511
		return GIT_SUCCESS;
512 513 514 515 516
	} else if (error < GIT_SUCCESS) {
		return git__rethrow(error, "Failed to read packed refs");
	} else if (!updated) {
		return GIT_SUCCESS;
	}
517

518 519 520 521 522 523
	/*
	 * At this point, we want to refresh the packed refs. We already
	 * have the contents in our buffer.
	 */

	git_hashtable_clear(ref_cache->packfile);
524

525 526
	buffer_start = (const char *)packfile.data;
	buffer_end = (const char *)(buffer_start) + packfile.len;
527

528 529 530 531 532 533
	while (buffer_start < buffer_end && buffer_start[0] == '#') {
		buffer_start = strchr(buffer_start, '\n');
		if (buffer_start == NULL) {
			error = GIT_EPACKEDREFSCORRUPTED;
			goto cleanup;
		}
534
		buffer_start++;
535
	}
536

537
	while (buffer_start < buffer_end) {
538
		reference_oid *ref = NULL;
539

Vicent Marti committed
540
		error = packed_parse_oid(&ref, repo, &buffer_start, buffer_end);
541 542 543
		if (error < GIT_SUCCESS)
			goto cleanup;

544
		if (buffer_start[0] == '^') {
Vicent Marti committed
545
			error = packed_parse_peel(ref, &buffer_start, buffer_end);
546 547
			if (error < GIT_SUCCESS)
				goto cleanup;
548
		}
549

Vicent Marti committed
550
		error = git_hashtable_insert(ref_cache->packfile, ref->ref.name, ref);
551
		if (error < GIT_SUCCESS) {
552
			reference_free((git_reference *)ref);
553
			goto cleanup;
554 555
		}
	}
556

Vicent Marti committed
557
	git_futils_freebuffer(&packfile);
558 559
	return GIT_SUCCESS;

560
cleanup:
561 562
	git_hashtable_free(ref_cache->packfile);
	ref_cache->packfile = NULL;
Vicent Marti committed
563
	git_futils_freebuffer(&packfile);
564
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load packed references");
565
}
566

567 568 569 570



struct dirent_list_data {
571
	git_repository *repo;
572 573
	size_t repo_path_len;
	unsigned int list_flags;
574 575 576

	int (*callback)(const char *, void *);
	void *callback_payload;
577 578 579 580 581
};

static int _dirent_loose_listall(void *_data, char *full_path)
{
	struct dirent_list_data *data = (struct dirent_list_data *)_data;
582
	char *file_path = full_path + data->repo_path_len;
583

Vicent Marti committed
584 585
	if (git_futils_isdir(full_path) == GIT_SUCCESS)
		return git_futils_direach(full_path, GIT_PATH_MAX, _dirent_loose_listall, _data);
586

587
	/* do not add twice a reference that exists already in the packfile */
588 589
	if ((data->list_flags & GIT_REF_PACKED) != 0 &&
		git_hashtable_lookup(data->repo->references.packfile, file_path) != NULL)
590 591
		return GIT_SUCCESS;

592 593 594 595
	if (data->list_flags != GIT_REF_LISTALL) {
		if ((data->list_flags & loose_guess_rtype(full_path)) == 0)
			return GIT_SUCCESS; /* we are filtering out this reference */
	}
596

597
	return data->callback(file_path, data->callback_payload);
598 599
}

Vicent Marti committed
600
static int _dirent_loose_load(void *data, char *full_path)
601
{
Vicent Marti committed
602
	git_repository *repository = (git_repository *)data;
603 604
	git_reference *reference;
	void *old_ref = NULL;
Vicent Marti committed
605 606
	char *file_path;
	int error;
607

Vicent Marti committed
608 609
	if (git_futils_isdir(full_path) == GIT_SUCCESS)
		return git_futils_direach(full_path, GIT_PATH_MAX, _dirent_loose_load, repository);
610

Vicent Marti committed
611 612 613 614
	file_path = full_path + strlen(repository->path_repository);
	error = loose_lookup(&reference, repository, file_path, 1);
	if (error == GIT_SUCCESS && reference != NULL) {
		reference->type |= GIT_REF_PACKED;
615

616
		if (git_hashtable_insert2(repository->references.packfile, reference->name, reference, &old_ref) < GIT_SUCCESS) {
Vicent Marti committed
617 618 619 620 621
			reference_free(reference);
			return GIT_ENOMEM;
		}

		if (old_ref != NULL)
622
			reference_free((git_reference *)old_ref);
Vicent Marti committed
623
	}
624

625
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load loose dirent");
626 627
}

Vicent Marti committed
628 629 630 631 632 633 634
/*
 * Load all the loose references from the repository
 * into the in-memory Packfile, and build a vector with
 * all the references so it can be written back to
 * disk.
 */
static int packed_loadloose(git_repository *repository)
635
{
Vicent Marti committed
636
	char refs_path[GIT_PATH_MAX];
637

Vicent Marti committed
638 639
	/* the packfile must have been previously loaded! */
	assert(repository->references.packfile);
640

Vicent Marti committed
641
	git_path_join(refs_path, repository->path_repository, GIT_REFS_DIR);
642

Vicent Marti committed
643 644
	/* Remove any loose references from the cache */
	{
645
		const void *GIT_UNUSED(_unused);
Vicent Marti committed
646
		git_reference *reference;
647

Vicent Marti committed
648 649 650 651 652 653 654 655 656 657
		GIT_HASHTABLE_FOREACH(repository->references.loose_cache, _unused, reference,
			reference_free(reference);
		);
	}

	git_hashtable_clear(repository->references.loose_cache);

	/*
	 * Load all the loose files from disk into the Packfile table.
	 * This will overwrite any old packed entries with their
658
	 * updated loose versions
Vicent Marti committed
659
	 */
Vicent Marti committed
660
	return git_futils_direach(refs_path, GIT_PATH_MAX, _dirent_loose_load, repository);
661
}
662

Vicent Marti committed
663 664 665 666
/*
 * Write a single reference into a packfile
 */
static int packed_write_ref(reference_oid *ref, git_filebuf *file)
667
{
Vicent Marti committed
668 669
	int error;
	char oid[GIT_OID_HEXSZ + 1];
670

Vicent Marti committed
671 672
	git_oid_fmt(oid, &ref->oid);
	oid[GIT_OID_HEXSZ] = 0;
673

674
	/*
Vicent Marti committed
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
	 * For references that peel to an object in the repo, we must
	 * write the resulting peel on a separate line, e.g.
	 *
	 *	6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
	 *	^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
	 *
	 * This obviously only applies to tags.
	 * The required peels have already been loaded into `ref->peel_target`.
	 */
	if (ref->ref.type & GIT_REF_HAS_PEEL) {
		char peel[GIT_OID_HEXSZ + 1];
		git_oid_fmt(peel, &ref->peel_target);
		peel[GIT_OID_HEXSZ] = 0;

		error = git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->ref.name, peel);
	} else {
		error = git_filebuf_printf(file, "%s %s\n", oid, ref->ref.name);
	}

694
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference");
695 696
}

Vicent Marti committed
697 698 699
/*
 * Find out what object this reference resolves to.
 *
700
 * For references that point to a 'big' tag (e.g. an
Vicent Marti committed
701 702 703 704 705
 * actual tag object on the repository), we need to
 * cache on the packfile the OID of the object to
 * which that 'big tag' is pointing to.
 */
static int packed_find_peel(reference_oid *ref)
706
{
707
	git_object *object;
Vicent Marti committed
708
	int error;
Vicent Marti committed
709

Vicent Marti committed
710 711
	if (ref->ref.type & GIT_REF_HAS_PEEL)
		return GIT_SUCCESS;
712

Vicent Marti committed
713 714 715 716 717 718
	/*
	 * Only applies to tags, i.e. references
	 * in the /refs/tags folder
	 */
	if (git__prefixcmp(ref->ref.name, GIT_REFS_TAGS_DIR) != 0)
		return GIT_SUCCESS;
719

Vicent Marti committed
720
	/*
721
	 * Find the tagged object in the repository
Vicent Marti committed
722
	 */
723
	error = git_object_lookup(&object, ref->ref.owner, &ref->oid, GIT_OBJ_ANY);
Vicent Marti committed
724
	if (error < GIT_SUCCESS)
725
		return git__throw(GIT_EOBJCORRUPTED, "Failed to find packed reference");
726

Vicent Marti committed
727
	/*
728 729 730
	 * If the tagged object is a Tag object, we need to resolve it;
	 * if the ref is actually a 'weak' ref, we don't need to resolve
	 * anything.
Vicent Marti committed
731
	 */
732 733
	if (git_object_type(object) == GIT_OBJ_TAG) {
		git_tag *tag = (git_tag *)object;
734

735 736 737 738 739 740 741 742 743 744 745 746
		/*
		 * Find the object pointed at by this tag
		 */
		git_oid_cpy(&ref->peel_target, git_tag_target_oid(tag));
		ref->ref.type |= GIT_REF_HAS_PEEL;

		/*
		 * The reference has now cached the resolved OID, and is
		 * marked at such. When written to the packfile, it'll be
		 * accompanied by this resolved oid
		 */
	}
Vicent Marti committed
747

Vicent Marti committed
748 749
	git_object_close(object);

Vicent Marti committed
750
	return GIT_SUCCESS;
751
}
752

Vicent Marti committed
753 754 755 756 757 758 759 760 761 762 763 764
/*
 * Remove all loose references
 *
 * Once we have successfully written a packfile,
 * all the loose references that were packed must be
 * removed from disk.
 *
 * This is a dangerous method; make sure the packfile
 * is well-written, because we are destructing references
 * here otherwise.
 */
static int packed_remove_loose(git_repository *repo, git_vector *packing_list)
765
{
Vicent Marti committed
766 767 768
	unsigned int i;
	char full_path[GIT_PATH_MAX];
	int error = GIT_SUCCESS;
769
	git_reference *reference;
Vicent Marti committed
770 771 772

	for (i = 0; i < packing_list->length; ++i) {
		git_reference *ref = git_vector_get(packing_list, i);
773 774 775 776 777 778 779 780

		/* Ensure the packed reference doesn't exist
		 * in a (more up-to-date?) state as a loose reference
		 */
		reference = git_hashtable_lookup(ref->owner->references.loose_cache, ref->name);
		if (reference != NULL)
			continue;

Vicent Marti committed
781
		git_path_join(full_path, repo->path_repository, ref->name);
Vicent Marti committed
782

Vicent Marti committed
783 784
		if (git_futils_exists(full_path) == GIT_SUCCESS &&
			p_unlink(full_path) < GIT_SUCCESS)
Vicent Marti committed
785 786 787 788 789 790 791 792 793 794 795 796 797
			error = GIT_EOSERR;

		/*
		 * if we fail to remove a single file, this is *not* good,
		 * but we should keep going and remove as many as possible.
		 * After we've removed as many files as possible, we return
		 * the error code anyway.
		 *
		 * TODO: mark this with a very special error code?
		 * GIT_EFAILTORMLOOSE
		 */
	}

798
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to remove loose packed reference");
799
}
800

Vicent Marti committed
801
static int packed_sort(const void *a, const void *b)
802
{
803 804
	const git_reference *ref_a = (const git_reference *)a;
	const git_reference *ref_b = (const git_reference *)b;
Vicent Marti committed
805 806

	return strcmp(ref_a->name, ref_b->name);
807 808
}

Vicent Marti committed
809 810 811 812
/*
 * Write all the contents in the in-memory packfile to disk.
 */
static int packed_write(git_repository *repo)
813
{
Vicent Marti committed
814 815 816 817
	git_filebuf pack_file;
	int error;
	unsigned int i;
	char pack_file_path[GIT_PATH_MAX];
818

Vicent Marti committed
819 820
	git_vector packing_list;
	size_t total_refs;
821

Vicent Marti committed
822
	assert(repo && repo->references.packfile);
823

Vicent Marti committed
824
	total_refs = repo->references.packfile->key_count;
825
	if ((error = git_vector_init(&packing_list, total_refs, packed_sort)) < GIT_SUCCESS)
826
		return git__rethrow(error, "Failed to write packed reference");
827

Vicent Marti committed
828 829 830
	/* Load all the packfile into a vector */
	{
		git_reference *reference;
831
		const void *GIT_UNUSED(_unused);
Vicent Marti committed
832 833

		GIT_HASHTABLE_FOREACH(repo->references.packfile, _unused, reference,
Vicent Marti committed
834
			git_vector_insert(&packing_list, reference); /* cannot fail: vector already has the right size */
Vicent Marti committed
835
		);
836 837
	}

Vicent Marti committed
838 839
	/* sort the vector so the entries appear sorted on the packfile */
	git_vector_sort(&packing_list);
840

Vicent Marti committed
841
	/* Now we can open the file! */
Vicent Marti committed
842
	git_path_join(pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE);
Vicent Marti committed
843
	if ((error = git_filebuf_open(&pack_file, pack_file_path, 0)) < GIT_SUCCESS)
844
		return git__rethrow(error, "Failed to write packed reference");
845

846 847 848
	/* Packfiles have a header... apparently
	 * This is in fact not required, but we might as well print it
	 * just for kicks */
Vicent Marti committed
849
	if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < GIT_SUCCESS)
850
		return git__rethrow(error, "Failed to write packed reference");
851

Vicent Marti committed
852 853
	for (i = 0; i < packing_list.length; ++i) {
		reference_oid *ref = (reference_oid *)git_vector_get(&packing_list, i);
854

Vicent Marti committed
855 856 857
		/* only direct references go to the packfile; otherwise
		 * this is a disaster */
		assert(ref->ref.type & GIT_REF_OID);
858

859 860
		if ((error = packed_find_peel(ref)) < GIT_SUCCESS) {
			error = git__throw(GIT_EOBJCORRUPTED, "A reference cannot be peeled");
Vicent Marti committed
861
			goto cleanup;
862 863
		}

864

Vicent Marti committed
865 866 867
		if ((error = packed_write_ref(ref, &pack_file)) < GIT_SUCCESS)
			goto cleanup;
	}
868

Vicent Marti committed
869 870 871 872 873 874 875 876
cleanup:
	/* if we've written all the references properly, we can commit
	 * the packfile to make the changes effective */
	if (error == GIT_SUCCESS) {
		error = git_filebuf_commit(&pack_file);

		/* when and only when the packfile has been properly written,
		 * we can go ahead and remove the loose refs */
877 878 879
		if (error == GIT_SUCCESS) {
			struct stat st;

Vicent Marti committed
880
			error = packed_remove_loose(repo, &packing_list);
881

Vicent Marti committed
882
			if (p_stat(pack_file_path, &st) == GIT_SUCCESS)
883 884
				repo->references.packfile_time = st.st_mtime;
		}
885
	}
Vicent Marti committed
886
	else git_filebuf_cleanup(&pack_file);
887

Vicent Marti committed
888
	git_vector_free(&packing_list);
889

890
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference");
Vicent Marti committed
891
}
892

893 894
static int _reference_available_cb(const char *ref, void *data)
{
895 896 897
	const char *new, *old;
	git_vector *refs;

898 899
	assert(ref && data);

900
	refs = (git_vector *)data;
901

902 903
	new = (const char *)git_vector_get(refs, 0);
	old = (const char *)git_vector_get(refs, 1);
904 905 906 907 908 909 910 911

	if (!old || strcmp(old, ref)) {
		int reflen = strlen(ref);
		int newlen = strlen(new);
		int cmplen = reflen < newlen ? reflen : newlen;
		const char *lead = reflen < newlen ? new : ref;

		if (!strncmp(new, ref, cmplen) &&
Vicent Marti committed
912
			lead[cmplen] == '/')
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
			return GIT_EEXISTS;
	}

	return GIT_SUCCESS;
}

static int reference_available(git_repository *repo, const char *ref, const char* old_ref)
{
	int error;
	git_vector refs;

	if (git_vector_init(&refs, 2, NULL) < GIT_SUCCESS)
		return GIT_ENOMEM;

	git_vector_insert(&refs, (void *)ref);
	git_vector_insert(&refs, (void *)old_ref);

930
	error = git_reference_foreach(repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&refs);
931 932 933

	git_vector_free(&refs);

934
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__throw(GIT_EEXISTS, "Reference name `%s` conflicts with existing reference", ref);
935 936
}

Vicent Marti committed
937 938 939 940 941 942 943
/*****************************************
 * External Library API
 *****************************************/

/**
 * Constructors
 */
944
int git_reference_lookup(git_reference **ref_out, git_repository *repo, const char *name)
945 946
{
	int error;
947
	char normalized_name[GIT_REFNAME_MAX];
948 949 950 951 952

	assert(ref_out && repo && name);

	*ref_out = NULL;

953
	error = normalize_name(normalized_name, sizeof(normalized_name), name, 0);
954
	if (error < GIT_SUCCESS)
955
		return git__rethrow(error, "Failed to lookup reference");
956

Vicent Marti committed
957 958
	/* First, check has been previously loaded and cached */
	*ref_out = git_hashtable_lookup(repo->references.loose_cache, normalized_name);
959
	if (*ref_out != NULL)
960
		return loose_update(*ref_out);
961

Vicent Marti committed
962 963
	/* Then check if there is a loose file for that reference.*/
	error = loose_lookup(ref_out, repo, normalized_name, 0);
964

Vicent Marti committed
965
	/* If the file exists, we store it on the cache */
966
	if (error == GIT_SUCCESS)
Vicent Marti committed
967
		return git_hashtable_insert(repo->references.loose_cache, (*ref_out)->name, (*ref_out));
968

Vicent Marti committed
969 970
	/* The loose lookup has failed, but not because the reference wasn't found;
	 * probably the loose reference is corrupted. this is bad. */
971
	if (error != GIT_ENOTFOUND)
972
		return git__rethrow(error, "Failed to lookup reference");
973

Vicent Marti committed
974 975
	/*
	 * If we cannot find a loose reference, we look into the packfile
976
	 * Load the packfile first if it hasn't been loaded
Vicent Marti committed
977
	 */
978 979 980
	/* load all the packed references */
	error = packed_load(repo);
	if (error < GIT_SUCCESS)
981
		return git__rethrow(error, "Failed to lookup reference");
982

Vicent Marti committed
983 984
	/* Look up on the packfile */
	*ref_out = git_hashtable_lookup(repo->references.packfile, normalized_name);
985 986 987
	if (*ref_out != NULL)
		return GIT_SUCCESS;

988
	/* The reference doesn't exist anywhere */
989
	return git__throw(GIT_ENOTFOUND, "Failed to lookup reference. Reference doesn't exist");
990
}
991

Vicent Marti committed
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
/**
 * Getters
 */
git_rtype git_reference_type(git_reference *ref)
{
	assert(ref);

	if (ref->type & GIT_REF_OID)
		return GIT_REF_OID;

	if (ref->type & GIT_REF_SYMBOLIC)
		return GIT_REF_SYMBOLIC;

	return GIT_REF_INVALID;
}

const char *git_reference_name(git_reference *ref)
{
	assert(ref);
	return ref->name;
}

git_repository *git_reference_owner(git_reference *ref)
{
	assert(ref);
	return ref->owner;
}

const git_oid *git_reference_oid(git_reference *ref)
{
	assert(ref);

	if ((ref->type & GIT_REF_OID) == 0)
		return NULL;

1027 1028 1029
	if (loose_update(ref) < GIT_SUCCESS)
		return NULL;

Vicent Marti committed
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
	return &((reference_oid *)ref)->oid;
}

const char *git_reference_target(git_reference *ref)
{
	assert(ref);

	if ((ref->type & GIT_REF_SYMBOLIC) == 0)
		return NULL;

1040 1041 1042
	if (loose_update(ref) < GIT_SUCCESS)
		return NULL;

Vicent Marti committed
1043 1044 1045
	return ((reference_symbolic *)ref)->target;
}

1046 1047 1048 1049
int git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force)
{
	char normalized[GIT_REFNAME_MAX];
	int error = GIT_SUCCESS, updated = 0;
1050 1051
	git_reference *ref = NULL;
	void *old_ref = NULL;
1052 1053 1054 1055 1056 1057 1058 1059 1060 1061

	if (git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force)
		return git__throw(GIT_EEXISTS, "Failed to create symbolic reference. Reference already exists");

	/*
	 * If they old ref was of the same type, then we can just update
	 * it (once we've checked that the target is valid). Otherwise we
	 * need a new reference because we can't make a symbolic ref out
	 * of an oid one.
	 * If if didn't exist, then we need to create a new one anyway.
Vicent Marti committed
1062
		*/
1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
	if (ref && ref->type & GIT_REF_SYMBOLIC){
		updated = 1;
	} else {
		ref = NULL;
		error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC);
		if (error < GIT_SUCCESS)
			goto cleanup;
	}

	/* The target can aither be the name of an object id reference or the name of another symbolic reference */
	error = normalize_name(normalized, sizeof(normalized), target, 0);
	if (error < GIT_SUCCESS)
		goto cleanup;

	/* set the target; this will write the reference on disk */
	error = git_reference_set_target(ref, normalized);
	if (error < GIT_SUCCESS)
		goto cleanup;

	/*
	 * If we didn't update the ref, then we need to insert or replace
	 * it in the loose cache. If we replaced a ref, free it.
	 */
	if (!updated){
1087
		error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, &old_ref);
1088 1089 1090
		if (error < GIT_SUCCESS)
			goto cleanup;

1091 1092
		if (old_ref != NULL)
			reference_free((git_reference *)old_ref);
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
	}

	*ref_out = ref;

	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference");

cleanup:
	reference_free(ref);
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference");
}

int git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force)
{
	int error = GIT_SUCCESS, updated = 0;
1107 1108
	git_reference *ref = NULL;
	void *old_ref = NULL;
1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121

	if(git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force)
		return git__throw(GIT_EEXISTS, "Failed to create reference OID. Reference already exists");

	if ((error = reference_available(repo, name, NULL)) < GIT_SUCCESS)
		return git__rethrow(error, "Failed to create reference");

	/*
	 * If they old ref was of the same type, then we can just update
	 * it (once we've checked that the target is valid). Otherwise we
	 * need a new reference because we can't make a symbolic ref out
	 * of an oid one.
	 * If if didn't exist, then we need to create a new one anyway.
Vicent Marti committed
1122
		*/
1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137
	if (ref && ref-> type & GIT_REF_OID){
		updated = 1;
	} else {
		ref = NULL;
		error = reference_create(&ref, repo, name, GIT_REF_OID);
		if (error < GIT_SUCCESS)
			goto cleanup;
	}

	/* set the oid; this will write the reference on disk */
	error = git_reference_set_oid(ref, id);
	if (error < GIT_SUCCESS)
		goto cleanup;

	if(!updated){
1138
		error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, &old_ref);
1139 1140 1141
		if (error < GIT_SUCCESS)
			goto cleanup;

1142 1143
		if (old_ref != NULL)
			reference_free((git_reference *)old_ref);
1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154
	}

	*ref_out = ref;

	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID");

cleanup:
	reference_free(ref);
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID");
}

Vicent Marti committed
1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185
/**
 * Setters
 */

/*
 * Change the OID target of a reference.
 *
 * For loose references, just change the oid in memory
 * and overwrite the file in disk.
 *
 * For packed files, this is not pretty:
 * For performance reasons, we write the new reference
 * loose on disk (it replaces the old on the packfile),
 * but we cannot invalidate the pointer to the reference,
 * and most importantly, the `packfile` object must stay
 * consistent with the representation of the packfile
 * on disk. This is what we need to:
 *
 * 1. Copy the reference
 * 2. Change the oid on the original
 * 3. Write the original to disk
 * 4. Write the original to the loose cache
 * 5. Replace the original with the copy (old reference) in the packfile cache
 */
int git_reference_set_oid(git_reference *ref, const git_oid *id)
{
	reference_oid *ref_oid;
	reference_oid *ref_old = NULL;
	int error = GIT_SUCCESS;

	if ((ref->type & GIT_REF_OID) == 0)
1186
		return git__throw(GIT_EINVALIDREFSTATE, "Failed to set OID target of reference. Not an OID reference");
Vicent Marti committed
1187 1188 1189

	ref_oid = (reference_oid *)ref;

1190 1191 1192 1193 1194
	assert(ref->owner);

	/* Don't let the user create references to OIDs that
	 * don't exist in the ODB */
	if (!git_odb_exists(git_repository_database(ref->owner), id))
1195
		return git__throw(GIT_ENOTFOUND, "Failed to set OID target of reference. OID doesn't exist in ODB");
1196

Vicent Marti committed
1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
	/* duplicate the reference;
	 * this copy will stay on the packfile cache */
	if (ref->type & GIT_REF_PACKED) {
		ref_old = git__malloc(sizeof(reference_oid));
		if (ref_old == NULL)
			return GIT_ENOMEM;

		ref_old->ref.name = git__strdup(ref->name);
		if (ref_old->ref.name == NULL) {
			free(ref_old);
			return GIT_ENOMEM;
		}
	}

	git_oid_cpy(&ref_oid->oid, id);
	ref->type &= ~GIT_REF_HAS_PEEL;

1214
	error = loose_write(ref);
Vicent Marti committed
1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235
	if (error < GIT_SUCCESS)
		goto cleanup;

	if (ref->type & GIT_REF_PACKED) {
		/* insert the original on the loose cache */
		error = git_hashtable_insert(ref->owner->references.loose_cache, ref->name, ref);
		if (error < GIT_SUCCESS)
			goto cleanup;

		ref->type &= ~GIT_REF_PACKED;

		/* replace the original in the packfile with the copy */
		error = git_hashtable_insert(ref->owner->references.packfile, ref_old->ref.name, ref_old);
		if (error < GIT_SUCCESS)
			goto cleanup;
	}

	return GIT_SUCCESS;

cleanup:
	reference_free((git_reference *)ref_old);
1236
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set OID target of reference");
Vicent Marti committed
1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250
}

/*
 * Change the target of a symbolic reference.
 *
 * This is easy because symrefs cannot be inside
 * a pack. We just change the target in memory
 * and overwrite the file on disk.
 */
int git_reference_set_target(git_reference *ref, const char *target)
{
	reference_symbolic *ref_sym;

	if ((ref->type & GIT_REF_SYMBOLIC) == 0)
1251
		return git__throw(GIT_EINVALIDREFSTATE, "Failed to set reference target. Not a symbolic reference");
Vicent Marti committed
1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266

	ref_sym = (reference_symbolic *)ref;

	free(ref_sym->target);
	ref_sym->target = git__strdup(target);
	if (ref_sym->target == NULL)
		return GIT_ENOMEM;

	return loose_write(ref);
}

/**
 * Other
 */

1267 1268 1269
int git_reference_rename(git_reference *ref, const char *new_name, int force)
{
	int error;
1270
	char *old_name = NULL;
1271 1272

	char aux_path[GIT_PATH_MAX];
1273
	char normalized[GIT_REFNAME_MAX];
1274

1275 1276 1277
	const char *target_ref = NULL;
	const char *head_target = NULL;
	const git_oid *target_oid = NULL;
1278
	git_reference *new_ref = NULL, *head = NULL;
1279 1280 1281

	assert(ref);

1282
	error = normalize_name(normalized, sizeof(normalized), new_name, ref->type & GIT_REF_OID);
1283
	if (error < GIT_SUCCESS)
1284
		return git__rethrow(error, "Failed to rename reference. Invalid name");
1285

1286
	new_name = normalized;
1287

1288
	error = git_reference_lookup(&new_ref, ref->owner, new_name);
1289 1290 1291 1292 1293 1294
	if (error == GIT_SUCCESS) {
		if (!force)
			return git__throw(GIT_EEXISTS, "Failed to rename reference. Reference already exists");

		error = git_reference_delete(new_ref);
	}
1295

schu committed
1296 1297 1298 1299 1300 1301 1302
	if (error < GIT_SUCCESS) {
		git_path_join(aux_path, ref->owner->path_repository, new_name);
		/* If we couldn't read the reference because it doesn't
		 * exist it's ok - otherwise return */
		if (git_futils_isfile(aux_path) == GIT_SUCCESS)
			goto cleanup;
	}
1303 1304

	if ((error = reference_available(ref->owner, new_name, ref->name)) < GIT_SUCCESS)
1305
		return git__rethrow(error, "Failed to rename reference. Reference already exists");
1306

1307 1308 1309 1310 1311
	/*
	 * First, we backup the reference targets. Just keeping the old
	 * reference won't work, since we may have to remove it to create
	 * the new reference, e.g. when renaming foo/bar -> foo.
	 */
1312

1313 1314
	old_name = git__strdup(ref->name);

1315 1316 1317 1318 1319 1320
	if (ref->type & GIT_REF_SYMBOLIC) {
		if ((target_ref = git_reference_target(ref)) == NULL)
			goto cleanup;
	} else {
		if ((target_oid = git_reference_oid(ref)) == NULL)
			goto cleanup;
1321 1322
	}

1323 1324 1325 1326
	/*
	 * Now delete the old ref and remove an possibly existing directory
	 * named `new_name`.
	 */
1327

1328
	if (ref->type & GIT_REF_PACKED) {
1329 1330
		ref->type &= ~GIT_REF_PACKED;

1331
		git_hashtable_remove(ref->owner->references.packfile, old_name);
1332 1333
		if ((error = packed_write(ref->owner)) < GIT_SUCCESS)
			goto rollback;
1334
	} else {
1335 1336
		git_path_join(aux_path, ref->owner->path_repository, old_name);
		if ((error = p_unlink(aux_path)) < GIT_SUCCESS)
1337 1338 1339 1340 1341
			goto cleanup;

		git_hashtable_remove(ref->owner->references.loose_cache, old_name);
	}

1342 1343 1344 1345
	git_path_join(aux_path, ref->owner->path_repository, new_name);
	if (git_futils_exists(aux_path) == GIT_SUCCESS) {
		if (git_futils_isdir(aux_path) == GIT_SUCCESS) {
			if ((error = git_futils_rmdir_r(aux_path, 0)) < GIT_SUCCESS)
1346 1347 1348
				goto rollback;
		} else goto rollback;
	}
1349

1350 1351 1352 1353 1354 1355
	/*
	 * Crude hack: delete any logs till we support proper reflogs.
	 * Otherwise git.git will possibly fail and leave a mess. git.git
	 * writes reflogs by default in any repo with a working directory:
	 *
	 * "We only enable reflogs in repositories that have a working directory
Vicent Marti committed
1356 1357 1358 1359
	 * associated with them, as shared/bare repositories do not have
	 * an easy means to prune away old log entries, or may fail logging
	 * entirely if the user's gecos information is not valid during a push.
	 * This heuristic was suggested on the mailing list by Junio."
1360 1361 1362 1363 1364 1365 1366
	 *
	 * 	Shawn O. Pearce - 0bee59186976b1d9e6b2dd77332480c9480131d5
	 *
	 * TODO
	 *
	 */

1367 1368 1369 1370
	git_path_join_n(aux_path, 3, ref->owner->path_repository, "logs", old_name);
	if (git_futils_isfile(aux_path) == GIT_SUCCESS) {
		if ((error = p_unlink(aux_path)) < GIT_SUCCESS)
			goto rollback;
1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382
	}

	/*
	 * Finally we can create the new reference.
	 */
	if (ref->type & GIT_REF_SYMBOLIC) {
		if ((error = git_reference_create_symbolic(&new_ref, ref->owner, new_name, target_ref, 0)) < GIT_SUCCESS)
			goto rollback;
	} else {
		if ((error = git_reference_create_oid(&new_ref, ref->owner, new_name, target_oid, 0)) < GIT_SUCCESS)
			goto rollback;
	}
1383 1384

	free(ref->name);
1385 1386
	ref->name = new_ref->name;

1387 1388 1389 1390 1391 1392 1393
	/*
	 * No need in new_ref anymore. We created it to fix the change on disk.
	 * TODO: Refactoring required.
	 */
	new_ref->name = NULL;
	reference_free(new_ref);

schu committed
1394
	if ((error = git_hashtable_insert(ref->owner->references.loose_cache, ref->name, ref)) < GIT_SUCCESS)
1395
		goto rollback;
1396

1397 1398 1399
	/*
	 * Check if we have to update HEAD.
	 */
1400

1401 1402
	if ((error = git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE)) < GIT_SUCCESS)
		goto cleanup;
1403

1404
	head_target = git_reference_target(head);
1405

1406
	if (head_target && !strcmp(head_target, old_name))
1407
		if ((error = git_reference_create_symbolic(&head, ref->owner, "HEAD", ref->name, 1)) < GIT_SUCCESS)
1408
			goto rollback;
1409

1410 1411
cleanup:
	free(old_name);
1412 1413
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference");

1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426
rollback:
	/*
	 * Try to create the old reference again.
	 */
	if (ref->type & GIT_REF_SYMBOLIC)
		error = git_reference_create_symbolic(&new_ref, ref->owner, old_name, target_ref, 0);
	else
		error = git_reference_create_oid(&new_ref, ref->owner, old_name, target_oid, 0);

	ref->name = old_name;

	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference. Failed to rollback");
}
1427 1428

/*
Vicent Marti committed
1429 1430 1431 1432 1433 1434
 * Delete a reference.
 *
 * If the reference is packed, this is an expensive
 * operation. We need to remove the reference from
 * the memory cache and then rewrite the whole pack
 *
1435
 * If the reference is loose, we remove it on
Vicent Marti committed
1436
 * the filesystem and update the in-memory cache
1437 1438 1439
 * accordingly. We also make sure that an older version
 * of it doesn't exist as a packed reference. If this
 * is the case, this packed reference is removed as well.
Vicent Marti committed
1440 1441 1442 1443 1444 1445
 *
 * This obviously invalidates the `ref` pointer.
 */
int git_reference_delete(git_reference *ref)
{
	int error;
1446
	git_reference *reference;
Vicent Marti committed
1447 1448 1449 1450

	assert(ref);

	if (ref->type & GIT_REF_PACKED) {
1451 1452
		/* load the existing packfile */
		if ((error = packed_load(ref->owner)) < GIT_SUCCESS)
1453
			return git__rethrow(error, "Failed to delete reference");
1454

1455 1456 1457
		if (git_hashtable_remove(ref->owner->references.packfile, ref->name) < GIT_SUCCESS)
			return git__throw(GIT_ENOTFOUND, "Reference not found");

Vicent Marti committed
1458 1459 1460
		error = packed_write(ref->owner);
	} else {
		char full_path[GIT_PATH_MAX];
Vicent Marti committed
1461
		git_path_join(full_path, ref->owner->path_repository, ref->name);
Vicent Marti committed
1462
		git_hashtable_remove(ref->owner->references.loose_cache, ref->name);
Vicent Marti committed
1463
		error = p_unlink(full_path);
1464 1465 1466 1467 1468 1469
		if (error < GIT_SUCCESS)
			goto cleanup;

		/* When deleting a loose reference, we have to ensure that an older
		 * packed version of it doesn't exist
		 */
1470
		if (!git_reference_lookup(&reference, ref->owner, ref->name)) {
1471 1472 1473
			assert((reference->type & GIT_REF_PACKED) != 0);
			error = git_reference_delete(reference);
		}
Vicent Marti committed
1474 1475
	}

1476
cleanup:
Vicent Marti committed
1477
	reference_free(ref);
1478
	return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to delete reference");
Vicent Marti committed
1479 1480 1481 1482 1483 1484 1485 1486 1487
}

int git_reference_resolve(git_reference **resolved_ref, git_reference *ref)
{
	git_repository *repo;
	int error, i;

	assert(resolved_ref && ref);
	*resolved_ref = NULL;
1488 1489

	if ((error = loose_update(ref)) < GIT_SUCCESS)
1490
		return git__rethrow(error, "Failed to resolve reference");
1491

Vicent Marti committed
1492 1493 1494 1495 1496
	repo = ref->owner;

	for (i = 0; i < MAX_NESTING_LEVEL; ++i) {
		reference_symbolic *ref_sym;

1497 1498
		*resolved_ref = ref;

Vicent Marti committed
1499
		if (ref->type & GIT_REF_OID)
Vicent Marti committed
1500 1501 1502
			return GIT_SUCCESS;

		ref_sym = (reference_symbolic *)ref;
1503
		if ((error = git_reference_lookup(&ref, repo, ref_sym->target)) < GIT_SUCCESS)
Vicent Marti committed
1504
			return error;
Vicent Marti committed
1505 1506
	}

Vicent Marti committed
1507
	return git__throw(GIT_ENOMEM, "Failed to resolve reference. Reference is too nested");
Vicent Marti committed
1508 1509 1510 1511 1512 1513 1514 1515
}

int git_reference_packall(git_repository *repo)
{
	int error;

	/* load the existing packfile */
	if ((error = packed_load(repo)) < GIT_SUCCESS)
1516
		return git__rethrow(error, "Failed to pack references");
Vicent Marti committed
1517 1518 1519

	/* update it in-memory with all the loose references */
	if ((error = packed_loadloose(repo)) < GIT_SUCCESS)
1520
		return git__rethrow(error, "Failed to pack references");
Vicent Marti committed
1521 1522 1523 1524 1525

	/* write it back to disk */
	return packed_write(repo);
}

1526
int git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload)
1527 1528 1529 1530 1531
{
	int error;
	struct dirent_list_data data;
	char refs_path[GIT_PATH_MAX];

1532
	/* list all the packed references first */
1533 1534
	if (list_flags & GIT_REF_PACKED) {
		const char *ref_name;
1535
		void *GIT_UNUSED(_unused);
1536

1537
		if ((error = packed_load(repo)) < GIT_SUCCESS)
1538
			return git__rethrow(error, "Failed to list references");
1539 1540

		GIT_HASHTABLE_FOREACH(repo->references.packfile, ref_name, _unused,
1541
			if ((error = callback(ref_name, payload)) < GIT_SUCCESS)
Vicent Marti committed
1542
				return git__throw(error, "Failed to list references. User callback failed");
1543 1544 1545
		);
	}

1546 1547
	/* now list the loose references, trying not to
	 * duplicate the ref names already in the packed-refs file */
1548 1549 1550 1551 1552 1553 1554 1555

	data.repo_path_len = strlen(repo->path_repository);
	data.list_flags = list_flags;
	data.repo = repo;
	data.callback = callback;
	data.callback_payload = payload;


Vicent Marti committed
1556 1557
	git_path_join(refs_path, repo->path_repository, GIT_REFS_DIR);
	return git_futils_direach(refs_path, GIT_PATH_MAX, _dirent_loose_listall, &data);
1558 1559
}

1560
static int cb__reflist_add(const char *ref, void *data)
1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577
{
	return git_vector_insert((git_vector *)data, git__strdup(ref));
}

int git_reference_listall(git_strarray *array, git_repository *repo, unsigned int list_flags)
{
	int error;
	git_vector ref_list;

	assert(array && repo);

	array->strings = NULL;
	array->count = 0;

	if (git_vector_init(&ref_list, 8, NULL) < GIT_SUCCESS)
		return GIT_ENOMEM;

1578
	error = git_reference_foreach(repo, list_flags, &cb__reflist_add, (void *)&ref_list);
1579 1580

	if (error < GIT_SUCCESS) {
1581
		git_vector_free(&ref_list);
Vicent Marti committed
1582
		return error;
1583 1584
	}

1585 1586
	array->strings = (char **)ref_list.contents;
	array->count = ref_list.length;
1587 1588
	return GIT_SUCCESS;
}
Vicent Marti committed
1589 1590 1591 1592 1593 1594 1595




/*****************************************
 * Init/free (repository API)
 *****************************************/
1596 1597 1598
int git_repository__refcache_init(git_refcache *refs)
{
	assert(refs);
1599

Vicent Marti committed
1600
	refs->loose_cache = git_hashtable_alloc(
1601 1602
		default_table_size,
		reftable_hash,
1603
		(git_hash_keyeq_ptr)(&git__strcmp_cb));
1604

Vicent Marti committed
1605 1606
	/* packfile loaded lazily */
	refs->packfile = NULL;
1607
	refs->packfile_time = 0;
1608

Vicent Marti committed
1609
	return (refs->loose_cache) ? GIT_SUCCESS : GIT_ENOMEM;
1610
}
1611

1612 1613 1614
void git_repository__refcache_free(git_refcache *refs)
{
	git_reference *reference;
1615
	const void *GIT_UNUSED(_unused);
1616

1617 1618
	assert(refs);

Vicent Marti committed
1619
	GIT_HASHTABLE_FOREACH(refs->loose_cache, _unused, reference,
1620 1621 1622
		reference_free(reference);
	);

Vicent Marti committed
1623
	git_hashtable_free(refs->loose_cache);
1624

Vicent Marti committed
1625 1626 1627 1628 1629 1630 1631
	if (refs->packfile) {
		GIT_HASHTABLE_FOREACH(refs->packfile, _unused, reference,
			reference_free(reference);
		);

		git_hashtable_free(refs->packfile);
	}
1632
}
1633

Vicent Marti committed
1634 1635 1636 1637 1638


/*****************************************
 * Name normalization
 *****************************************/
1639 1640
static int check_valid_ref_char(char ch)
{
1641
	if ((unsigned) ch <= ' ')
Vicent Marti committed
1642
		return GIT_ERROR;
1643 1644 1645 1646 1647 1648 1649 1650

	switch (ch) {
	case '~':
	case '^':
	case ':':
	case '\\':
	case '?':
	case '[':
1651
	case '*':
Vicent Marti committed
1652
		return GIT_ERROR;
1653 1654 1655 1656 1657
	default:
		return GIT_SUCCESS;
	}
}

1658
static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref)
1659 1660
{
	const char *name_end, *buffer_out_start;
1661
	const char *current;
1662 1663 1664 1665 1666
	int contains_a_slash = 0;

	assert(name && buffer_out);

	buffer_out_start = buffer_out;
1667
	current = name;
1668 1669
	name_end = name + strlen(name);

1670 1671 1672
	/* Terminating null byte */
	out_size--;

1673 1674
	/* A refname can not be empty */
	if (name_end == name)
1675
		return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name is empty");
1676 1677 1678

	/* A refname can not end with a dot or a slash */
	if (*(name_end - 1) == '.' || *(name_end - 1) == '/')
1679
		return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with dot or slash");
1680

1681
	while (current < name_end && out_size) {
1682
		if (check_valid_ref_char(*current))
Vicent Marti committed
1683
			return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains invalid characters");
1684 1685 1686 1687 1688 1689

		if (buffer_out > buffer_out_start) {
			char prev = *(buffer_out - 1);

			/* A refname can not start with a dot nor contain a double dot */
			if (*current == '.' && ((prev == '.') || (prev == '/')))
1690
				return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name starts with a dot or contains a double dot");
1691 1692 1693

			/* '@{' is forbidden within a refname */
			if (*current == '{' && prev == '@')
1694
				return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains '@{'");
1695 1696 1697 1698 1699 1700 1701 1702

			/* Prevent multiple slashes from being added to the output */
			if (*current == '/' && prev == '/') {
				current++;
				continue;
			}
		}

1703
		if (*current == '/')
1704 1705 1706
			contains_a_slash = 1;

		*buffer_out++ = *current++;
1707
		out_size--;
1708 1709
	}

1710 1711 1712
	if (!out_size)
		return git__throw(GIT_EINVALIDREFNAME, "Reference name is too long");

1713
	/* Object id refname have to contain at least one slash, except
1714 1715 1716
	 * for HEAD in a detached state or MERGE_HEAD if we're in the
	 * middle of a merge */
	if (is_oid_ref && !contains_a_slash && (strcmp(name, GIT_HEAD_FILE) && strcmp(name, GIT_MERGE_HEAD_FILE)))
Vicent Marti committed
1717
		return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains no slashes");
1718 1719 1720

	/* A refname can not end with ".lock" */
	if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION))
Vicent Marti committed
1721
		return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with '.lock'");
1722 1723 1724

	*buffer_out = '\0';

1725 1726 1727 1728
	/*
	 * For object id references, name has to start with refs/. Again,
	 * we need to allow HEAD to be in a detached state.
	 */
Vicent Marti committed
1729 1730
	if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) ||
		strcmp(buffer_out_start, GIT_HEAD_FILE)))
1731
		return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name does not start with 'refs/'");
1732

Vicent Marti committed
1733
	return GIT_SUCCESS;
1734
}
1735

1736
int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name)
1737
{
1738
	return normalize_name(buffer_out, out_size, name, 0);
1739 1740
}

1741
int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name)
1742
{
1743
	return normalize_name(buffer_out, out_size, name, 1);
1744
}