branch.c 15.4 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3 4 5 6 7 8 9 10
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

#include "common.h"
#include "commit.h"
#include "tag.h"
11 12
#include "config.h"
#include "refspec.h"
13
#include "refs.h"
14
#include "remote.h"
15
#include "annotated_commit.h"
16

17 18
#include "git2/branch.h"

19 20 21 22 23 24
static int retrieve_branch_reference(
	git_reference **branch_reference_out,
	git_repository *repo,
	const char *branch_name,
	int is_remote)
{
Russell Belfer committed
25 26
	git_reference *branch = NULL;
	int error = 0;
27 28 29 30 31
	char *prefix;
	git_buf ref_name = GIT_BUF_INIT;

	prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR;

Russell Belfer committed
32 33 34 35 36 37
	if ((error = git_buf_joinpath(&ref_name, prefix, branch_name)) < 0)
		/* OOM */;
	else if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0)
		giterr_set(
			GITERR_REFERENCE, "Cannot locate %s branch '%s'",
			is_remote ? "remote-tracking" : "local", branch_name);
38

Russell Belfer committed
39
	*branch_reference_out = branch; /* will be NULL on error */
40 41 42 43 44

	git_buf_free(&ref_name);
	return error;
}

45
static int not_a_local_branch(const char *reference_name)
46
{
47 48 49
	giterr_set(
		GITERR_INVALID,
		"Reference '%s' is not a local branch.", reference_name);
50 51 52
	return -1;
}

53
static int create_branch(
54 55 56 57
	git_reference **ref_out,
	git_repository *repository,
	const char *branch_name,
	const git_commit *commit,
58
	const char *from,
59
	int force)
60
{
61
	int is_head = 0;
62
	git_reference *branch = NULL;
63
	git_buf canonical_branch_name = GIT_BUF_INIT,
64
			  log_message = GIT_BUF_INIT;
65
	int error = -1;
66

Vicent Marti committed
67 68
	assert(branch_name && commit && ref_out);
	assert(git_object_owner((const git_object *)commit) == repository);
69 70 71 72 73 74 75

	if (force && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) {
		error = git_branch_is_head(branch);
		git_reference_free(branch);
		branch = NULL;

		if (error < 0)
76
			goto cleanup;
77 78

		is_head = error;
79
	}
80

81 82
	if (is_head && force) {
		giterr_set(GITERR_REFERENCE, "Cannot force update branch '%s' as it is "
83 84
			"the current HEAD of the repository.", branch_name);
		error = -1;
85 86
		goto cleanup;
	}
nulltoken committed
87

88 89
	if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0)
		goto cleanup;
90

91
	if (git_buf_printf(&log_message, "branch: Created from %s", from) < 0)
92 93 94
		goto cleanup;

	error = git_reference_create(&branch, repository,
95
		git_buf_cstr(&canonical_branch_name), git_commit_id(commit), force,
96
		git_buf_cstr(&log_message));
97 98

	if (!error)
99
		*ref_out = branch;
100

101
cleanup:
102
	git_buf_free(&canonical_branch_name);
103
	git_buf_free(&log_message);
104 105 106
	return error;
}

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
int git_branch_create(
	git_reference **ref_out,
	git_repository *repository,
	const char *branch_name,
	const git_commit *commit,
	int force)
{
	return create_branch(ref_out, repository, branch_name, commit, git_oid_tostr_s(git_commit_id(commit)), force);
}

int git_branch_create_from_annotated(
	git_reference **ref_out,
	git_repository *repository,
	const char *branch_name,
	const git_annotated_commit *commit,
	int force)
{
	return create_branch(ref_out, repository, branch_name, commit->commit, commit->ref_name, force);
}

127
int git_branch_delete(git_reference *branch)
128
{
129
	int is_head;
130 131
	git_buf config_section = GIT_BUF_INIT;
	int error = -1;
132

133
	assert(branch);
134

135 136 137 138
	if (!git_reference_is_branch(branch) && !git_reference_is_remote(branch)) {
		giterr_set(GITERR_INVALID, "Reference '%s' is not a valid branch.",
			git_reference_name(branch));
		return GIT_ENOTFOUND;
139
	}
140

141 142
	if ((is_head = git_branch_is_head(branch)) < 0)
		return is_head;
143

144
	if (is_head) {
145 146
		giterr_set(GITERR_REFERENCE, "Cannot delete branch '%s' as it is "
			"the current HEAD of the repository.", git_reference_name(branch));
147
		return -1;
148 149
	}

150 151
	if (git_buf_join(&config_section, '.', "branch",
			git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
152 153 154
		goto on_error;

	if (git_config_rename_section(
155 156
		git_reference_owner(branch), git_buf_cstr(&config_section), NULL) < 0)
		goto on_error;
157

158
	error = git_reference_delete(branch);
159 160 161 162

on_error:
	git_buf_free(&config_section);
	return error;
163 164
}

165
typedef struct {
166
	git_reference_iterator *iter;
167 168 169
	unsigned int flags;
} branch_iter;

170
int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter)
171 172
{
	branch_iter *iter = (branch_iter *) _iter;
Vicent Marti committed
173
	git_reference *ref;
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
	int error;

	while ((error = git_reference_next(&ref, iter->iter)) == 0) {
		if ((iter->flags & GIT_BRANCH_LOCAL) &&
		    !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) {
			*out = ref;
			*out_type = GIT_BRANCH_LOCAL;

			return 0;
		} else  if ((iter->flags & GIT_BRANCH_REMOTE) &&
			    !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) {
			*out = ref;
			*out_type = GIT_BRANCH_REMOTE;

			return 0;
		} else {
			git_reference_free(ref);
		}
	}
193

194 195
	return error;
}
196

197 198 199
int git_branch_iterator_new(
	git_branch_iterator **out,
	git_repository *repo,
200
	git_branch_t list_flags)
201 202
{
	branch_iter *iter;
203

204 205
	iter = git__calloc(1, sizeof(branch_iter));
	GITERR_CHECK_ALLOC(iter);
Vicent Marti committed
206

207
	iter->flags = list_flags;
208

209 210 211
	if (git_reference_iterator_new(&iter->iter, repo) < 0) {
		git__free(iter);
		return -1;
212 213
	}

214
	*out = (git_branch_iterator *) iter;
215

216 217 218 219 220 221 222
	return 0;
}

void git_branch_iterator_free(git_branch_iterator *_iter)
{
	branch_iter *iter = (branch_iter *) _iter;

223 224 225
	if (iter == NULL)
		return;

226 227
	git_reference_iterator_free(iter->iter);
	git__free(iter);
228 229
}

230
int git_branch_move(
231
	git_reference **out,
232 233
	git_reference *branch,
	const char *new_branch_name,
234
	int force)
235
{
236
	git_buf new_reference_name = GIT_BUF_INIT,
237 238
	        old_config_section = GIT_BUF_INIT,
	        new_config_section = GIT_BUF_INIT,
239
	        log_message = GIT_BUF_INIT;
240
	int error;
241

242 243 244
	assert(branch && new_branch_name);

	if (!git_reference_is_branch(branch))
245
		return not_a_local_branch(git_reference_name(branch));
246

247
	if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
248
		goto done;
249

250
	if ((error = git_buf_printf(&log_message, "branch: renamed %s to %s",
251
				    git_reference_name(branch), git_buf_cstr(&new_reference_name))) < 0)
252 253
			goto done;

254
	/* first update ref then config so failure won't trash config */
Vicent Marti committed
255

256
	error = git_reference_rename(
257
		out, branch, git_buf_cstr(&new_reference_name), force,
258
		git_buf_cstr(&log_message));
259
	if (error < 0)
260
		goto done;
261

262 263 264 265 266 267 268 269
	git_buf_join(&old_config_section, '.', "branch",
		git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR));
	git_buf_join(&new_config_section, '.', "branch", new_branch_name);

	error = git_config_rename_section(
		git_reference_owner(branch),
		git_buf_cstr(&old_config_section),
		git_buf_cstr(&new_config_section));
270

271
done:
272
	git_buf_free(&new_reference_name);
273 274
	git_buf_free(&old_config_section);
	git_buf_free(&new_config_section);
275
	git_buf_free(&log_message);
276

277
	return error;
278
}
279 280

int git_branch_lookup(
281 282 283 284
	git_reference **ref_out,
	git_repository *repo,
	const char *branch_name,
	git_branch_t branch_type)
285 286 287 288 289
{
	assert(ref_out && repo && branch_name);

	return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE);
}
290

Jacques Germishuys committed
291 292 293
int git_branch_name(
	const char **out,
	const git_reference *ref)
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
{
	const char *branch_name;

	assert(out && ref);

	branch_name = ref->name;

	if (git_reference_is_branch(ref)) {
		branch_name += strlen(GIT_REFS_HEADS_DIR);
	} else if (git_reference_is_remote(ref)) {
		branch_name += strlen(GIT_REFS_REMOTES_DIR);
	} else {
		giterr_set(GITERR_INVALID,
				"Reference '%s' is neither a local nor a remote branch.", ref->name);
		return -1;
	}
	*out = branch_name;
	return 0;
}

314
static int retrieve_upstream_configuration(
315
	git_buf *out,
316
	const git_config *config,
317 318
	const char *canonical_branch_name,
	const char *format)
319 320 321 322 323
{
	git_buf buf = GIT_BUF_INIT;
	int error;

	if (git_buf_printf(&buf, format,
324
		canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0)
325 326
			return -1;

327
	error = git_config_get_string_buf(out, config, git_buf_cstr(&buf));
328 329 330 331
	git_buf_free(&buf);
	return error;
}

332 333
int git_branch_upstream_name(
	git_buf *out,
334
	git_repository *repo,
335
	const char *refname)
336
{
337 338
	git_buf remote_name = GIT_BUF_INIT;
	git_buf merge_name = GIT_BUF_INIT;
339 340 341 342
	git_buf buf = GIT_BUF_INIT;
	int error = -1;
	git_remote *remote = NULL;
	const git_refspec *refspec;
343
	git_config *config;
344

345 346 347
	assert(out && refname);

	git_buf_sanitize(out);
348

349 350
	if (!git_reference__is_branch(refname))
		return not_a_local_branch(refname);
351

352
	if ((error = git_repository_config_snapshot(&config, repo)) < 0)
353 354
		return error;

355
	if ((error = retrieve_upstream_configuration(
356
		&remote_name, config, refname, "branch.%s.remote")) < 0)
357
			goto cleanup;
358

359
	if ((error = retrieve_upstream_configuration(
360
		&merge_name, config, refname, "branch.%s.merge")) < 0)
361
			goto cleanup;
362

363
	if (git_buf_len(&remote_name) == 0 || git_buf_len(&merge_name) == 0) {
364
		giterr_set(GITERR_REFERENCE,
365
			"branch '%s' does not have an upstream", refname);
nulltoken committed
366 367 368
		error = GIT_ENOTFOUND;
		goto cleanup;
	}
369

370 371
	if (strcmp(".", git_buf_cstr(&remote_name)) != 0) {
		if ((error = git_remote_lookup(&remote, repo, git_buf_cstr(&remote_name))) < 0)
372 373
			goto cleanup;

374
		refspec = git_remote__matching_refspec(remote, git_buf_cstr(&merge_name));
375 376 377
		if (!refspec) {
			error = GIT_ENOTFOUND;
			goto cleanup;
378 379
		}

380
		if (git_refspec_transform(&buf, refspec, git_buf_cstr(&merge_name)) < 0)
381 382
			goto cleanup;
	} else
383
		if (git_buf_set(&buf, git_buf_cstr(&merge_name), git_buf_len(&merge_name)) < 0)
384 385
			goto cleanup;

386
	error = git_buf_set(out, git_buf_cstr(&buf), git_buf_len(&buf));
387 388

cleanup:
389
	git_config_free(config);
390
	git_remote_free(remote);
391 392
	git_buf_free(&remote_name);
	git_buf_free(&merge_name);
393 394 395
	git_buf_free(&buf);
	return error;
}
396

397 398 399 400 401 402 403 404
int git_branch_upstream_remote(git_buf *buf, git_repository *repo, const char *refname)
{
	int error;
	git_config *cfg;

	if (!git_reference__is_branch(refname))
		return not_a_local_branch(refname);

405
	if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
406 407
		return error;

408
	git_buf_sanitize(buf);
409

410 411 412 413
	if ((error = retrieve_upstream_configuration(buf, cfg, refname, "branch.%s.remote")) < 0)
		return error;

	if (git_buf_len(buf) == 0) {
414 415
		giterr_set(GITERR_REFERENCE, "branch '%s' does not have an upstream remote", refname);
		error = GIT_ENOTFOUND;
416
		git_buf_clear(buf);
417 418
	}

419 420 421
	return error;
}

422
int git_branch_remote_name(git_buf *buf, git_repository *repo, const char *refname)
423 424
{
	git_strarray remote_list = {0};
425
	size_t i;
426 427 428 429 430
	git_remote *remote;
	const git_refspec *fetchspec;
	int error = 0;
	char *remote_name = NULL;

431 432 433
	assert(buf && repo && refname);

	git_buf_sanitize(buf);
434 435

	/* Verify that this is a remote branch */
436
	if (!git_reference__is_remote(refname)) {
437
		giterr_set(GITERR_INVALID, "Reference '%s' is not a remote branch.",
438
			refname);
439 440 441 442 443 444 445 446 447 448
		error = GIT_ERROR;
		goto cleanup;
	}

	/* Get the remotes */
	if ((error = git_remote_list(&remote_list, repo)) < 0)
		goto cleanup;

	/* Find matching remotes */
	for (i = 0; i < remote_list.count; i++) {
449
		if ((error = git_remote_lookup(&remote, repo, remote_list.strings[i])) < 0)
450
			continue;
451

452
		fetchspec = git_remote__matching_dst_refspec(remote, refname);
453
		if (fetchspec) {
454 455 456 457 458 459 460 461
			/* If we have not already set out yet, then set
			 * it to the matching remote name. Otherwise
			 * multiple remotes match this reference, and it
			 * is ambiguous. */
			if (!remote_name) {
				remote_name = remote_list.strings[i];
			} else {
				git_remote_free(remote);
462 463

				giterr_set(GITERR_REFERENCE,
464
					"Reference '%s' is ambiguous", refname);
465 466 467 468 469 470 471 472 473
				error = GIT_EAMBIGUOUS;
				goto cleanup;
			}
		}

		git_remote_free(remote);
	}

	if (remote_name) {
474 475
		git_buf_clear(buf);
		error = git_buf_puts(buf, remote_name);
476
	} else {
477
		giterr_set(GITERR_REFERENCE,
478
			"Could not determine remote for '%s'", refname);
479 480 481 482
		error = GIT_ENOTFOUND;
	}

cleanup:
483 484 485
	if (error < 0)
		git_buf_free(buf);

486 487 488 489
	git_strarray_free(&remote_list);
	return error;
}

490
int git_branch_upstream(
Jacques Germishuys committed
491 492
	git_reference **tracking_out,
	const git_reference *branch)
493 494 495 496
{
	int error;
	git_buf tracking_name = GIT_BUF_INIT;

497
	if ((error = git_branch_upstream_name(&tracking_name,
498 499 500 501 502 503 504 505 506 507 508 509
		git_reference_owner(branch), git_reference_name(branch))) < 0)
			return error;

	error = git_reference_lookup(
		tracking_out,
		git_reference_owner(branch),
		git_buf_cstr(&tracking_name));

	git_buf_free(&tracking_name);
	return error;
}

510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
static int unset_upstream(git_config *config, const char *shortname)
{
	git_buf buf = GIT_BUF_INIT;

	if (git_buf_printf(&buf, "branch.%s.remote", shortname) < 0)
		return -1;

	if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
		goto on_error;

	git_buf_clear(&buf);
	if (git_buf_printf(&buf, "branch.%s.merge", shortname) < 0)
		goto on_error;

	if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
		goto on_error;

	git_buf_free(&buf);
	return 0;

on_error:
	git_buf_free(&buf);
	return -1;
}

int git_branch_set_upstream(git_reference *branch, const char *upstream_name)
{
	git_buf key = GIT_BUF_INIT, value = GIT_BUF_INIT;
	git_reference *upstream;
	git_repository *repo;
	git_remote *remote = NULL;
	git_config *config;
	const char *name, *shortname;
543
	int local, error;
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
	const git_refspec *fetchspec;

	name = git_reference_name(branch);
	if (!git_reference__is_branch(name))
		return not_a_local_branch(name);

	if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0)
		return -1;

	shortname = name + strlen(GIT_REFS_HEADS_DIR);

	if (upstream_name == NULL)
		return unset_upstream(config, shortname);

	repo = git_reference_owner(branch);

	/* First we need to figure out whether it's a branch or remote-tracking */
	if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_LOCAL) == 0)
		local = 1;
	else if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_REMOTE) == 0)
		local = 0;
565 566 567
	else {
		giterr_set(GITERR_REFERENCE,
			"Cannot set upstream for branch '%s'", shortname);
568
		return GIT_ENOTFOUND;
569
	}
570 571 572 573 574 575 576 577

	/*
	 * If it's local, the remote is "." and the branch name is
	 * simply the refname. Otherwise we need to figure out what
	 * the remote-tracking branch's name on the remote is and use
	 * that.
	 */
	if (local)
578
		error = git_buf_puts(&value, ".");
579
	else
580 581 582 583
		error = git_branch_remote_name(&value, repo, git_reference_name(upstream));

	if (error < 0)
		goto on_error;
584 585 586 587 588 589 590 591

	if (git_buf_printf(&key, "branch.%s.remote", shortname) < 0)
		goto on_error;

	if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
		goto on_error;

	if (local) {
592 593
		git_buf_clear(&value);
		if (git_buf_puts(&value, git_reference_name(upstream)) < 0)
594 595 596
			goto on_error;
	} else {
		/* Get the remoe-tracking branch's refname in its repo */
597
		if (git_remote_lookup(&remote, repo, git_buf_cstr(&value)) < 0)
598 599
			goto on_error;

600
		fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream));
601
		git_buf_clear(&value);
602
		if (!fetchspec || git_refspec_rtransform(&value, fetchspec, git_reference_name(upstream)) < 0)
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
			goto on_error;

		git_remote_free(remote);
		remote = NULL;
	}

	git_buf_clear(&key);
	if (git_buf_printf(&key, "branch.%s.merge", shortname) < 0)
		goto on_error;

	if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
		goto on_error;

	git_reference_free(upstream);
	git_buf_free(&key);
	git_buf_free(&value);

	return 0;

on_error:
	git_reference_free(upstream);
	git_buf_free(&key);
	git_buf_free(&value);
	git_remote_free(remote);

	return -1;
}

631
int git_branch_is_head(
632
		const git_reference *branch)
633 634 635
{
	git_reference *head;
	bool is_same = false;
636
	int error;
637 638 639 640 641 642

	assert(branch);

	if (!git_reference_is_branch(branch))
		return false;

643 644
	error = git_repository_head(&head, git_reference_owner(branch));

645
	if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
646 647 648
		return false;

	if (error < 0)
649 650 651 652 653 654 655 656 657 658
		return -1;

	is_same = strcmp(
		git_reference_name(branch),
		git_reference_name(head)) == 0;

	git_reference_free(head);

	return is_same;
}