branch.c 14.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

16 17
#include "git2/branch.h"

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

	prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR;

Russell Belfer committed
31 32 33 34 35 36
	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);
37

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

	git_buf_free(&ref_name);
	return error;
}

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

52
int git_branch_create(
53 54 55 56
	git_reference **ref_out,
	git_repository *repository,
	const char *branch_name,
	const git_commit *commit,
57 58 59
	int force,
	const git_signature *signature,
	const char *log_message)
60
{
61
	int is_head = 0;
62
	git_reference *branch = NULL;
63 64 65
	git_buf canonical_branch_name = GIT_BUF_INIT,
			  log_message_buf = GIT_BUF_INIT;
	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 87
		goto cleanup;
	}
	
88 89
	if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0)
		goto cleanup;
90

91 92 93 94 95 96 97 98
	if (git_buf_sets(&log_message_buf, log_message ? log_message : "Branch: created") < 0)
		goto cleanup;

	error = git_reference_create(&branch, repository,
		git_buf_cstr(&canonical_branch_name), git_commit_id(commit), force, signature,
		git_buf_cstr(&log_message_buf));

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

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

107
int git_branch_delete(git_reference *branch)
108
{
109
	int is_head;
110 111
	git_buf config_section = GIT_BUF_INIT;
	int error = -1;
112

113
	assert(branch);
114

115 116 117 118
	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;
119
	}
120

121 122
	if ((is_head = git_branch_is_head(branch)) < 0)
		return is_head;
123

124
	if (is_head) {
125 126
		giterr_set(GITERR_REFERENCE, "Cannot delete branch '%s' as it is "
			"the current HEAD of the repository.", git_reference_name(branch));
127
		return -1;
128 129
	}

130 131
	if (git_buf_join(&config_section, '.', "branch",
			git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
132 133 134
		goto on_error;

	if (git_config_rename_section(
135 136
		git_reference_owner(branch), git_buf_cstr(&config_section), NULL) < 0)
		goto on_error;
137

138 139 140
	if (git_reference_delete(branch) < 0)
		goto on_error;

141 142 143
	if (git_reflog_delete(git_reference_owner(branch), git_reference_name(branch)) < 0)
		goto on_error;

144 145 146 147 148
	error = 0;

on_error:
	git_buf_free(&config_section);
	return error;
149 150
}

151
typedef struct {
152
	git_reference_iterator *iter;
153 154 155
	unsigned int flags;
} branch_iter;

156
int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter)
157 158
{
	branch_iter *iter = (branch_iter *) _iter;
Vicent Marti committed
159
	git_reference *ref;
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
	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);
		}
	}
179

180 181
	return error;
}
182

183 184 185
int git_branch_iterator_new(
	git_branch_iterator **out,
	git_repository *repo,
186
	git_branch_t list_flags)
187 188
{
	branch_iter *iter;
189

190 191
	iter = git__calloc(1, sizeof(branch_iter));
	GITERR_CHECK_ALLOC(iter);
Vicent Marti committed
192

193
	iter->flags = list_flags;
194

195 196 197
	if (git_reference_iterator_new(&iter->iter, repo) < 0) {
		git__free(iter);
		return -1;
198 199
	}

200
	*out = (git_branch_iterator *) iter;
201

202 203 204 205 206 207 208
	return 0;
}

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

209 210 211
	if (iter == NULL)
		return;

212 213
	git_reference_iterator_free(iter->iter);
	git__free(iter);
214 215
}

216
int git_branch_move(
217
	git_reference **out,
218 219
	git_reference *branch,
	const char *new_branch_name,
220 221 222
	int force,
	const git_signature *signature,
	const char *log_message)
223
{
224
	git_buf new_reference_name = GIT_BUF_INIT,
225 226 227
	        old_config_section = GIT_BUF_INIT,
	        new_config_section = GIT_BUF_INIT,
	        log_message_buf = GIT_BUF_INIT;
228
	int error;
229

230 231 232
	assert(branch && new_branch_name);

	if (!git_reference_is_branch(branch))
233
		return not_a_local_branch(git_reference_name(branch));
234

235
	if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
236
		goto done;
237

238 239 240 241 242 243 244 245 246
	if (log_message) {
		if ((error = git_buf_sets(&log_message_buf, log_message)) < 0)
			goto done;
	} else {
		if ((error = git_buf_printf(&log_message_buf, "Branch: renamed %s to %s",
						git_reference_name(branch), git_buf_cstr(&new_reference_name))) < 0)
			goto done;
	}

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

249
	error = git_reference_rename(
250
		out, branch, git_buf_cstr(&new_reference_name), force,
251
		signature, git_buf_cstr(&log_message_buf));
252
	if (error < 0)
253
		goto done;
254

255 256 257 258 259 260 261 262
	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));
263

264
done:
265
	git_buf_free(&new_reference_name);
266 267
	git_buf_free(&old_config_section);
	git_buf_free(&new_config_section);
268
	git_buf_free(&log_message_buf);
269

270
	return error;
271
}
272 273

int git_branch_lookup(
274 275 276 277
	git_reference **ref_out,
	git_repository *repo,
	const char *branch_name,
	git_branch_t branch_type)
278 279 280 281 282
{
	assert(ref_out && repo && branch_name);

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

Jacques Germishuys committed
284 285 286
int git_branch_name(
	const char **out,
	const git_reference *ref)
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
{
	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;
}

307
static int retrieve_upstream_configuration(
308
	const char **out,
309
	const git_config *config,
310 311
	const char *canonical_branch_name,
	const char *format)
312 313 314 315 316
{
	git_buf buf = GIT_BUF_INIT;
	int error;

	if (git_buf_printf(&buf, format,
317
		canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0)
318 319 320 321 322 323 324
			return -1;

	error = git_config_get_string(out, config, git_buf_cstr(&buf));
	git_buf_free(&buf);
	return error;
}

325 326
int git_branch_upstream_name(
	git_buf *out,
327
	git_repository *repo,
328
	const char *refname)
329 330 331 332 333 334
{
	const char *remote_name, *merge_name;
	git_buf buf = GIT_BUF_INIT;
	int error = -1;
	git_remote *remote = NULL;
	const git_refspec *refspec;
335
	git_config *config;
336

337 338 339
	assert(out && refname);

	git_buf_sanitize(out);
340

341 342
	if (!git_reference__is_branch(refname))
		return not_a_local_branch(refname);
343

344
	if ((error = git_repository_config_snapshot(&config, repo)) < 0)
345 346
		return error;

347
	if ((error = retrieve_upstream_configuration(
348
		&remote_name, config, refname, "branch.%s.remote")) < 0)
349
			goto cleanup;
350

351
	if ((error = retrieve_upstream_configuration(
352
		&merge_name, config, refname, "branch.%s.merge")) < 0)
353
			goto cleanup;
354

nulltoken committed
355
	if (!*remote_name || !*merge_name) {
356
		giterr_set(GITERR_REFERENCE,
357
			"branch '%s' does not have an upstream", refname);
nulltoken committed
358 359 360
		error = GIT_ENOTFOUND;
		goto cleanup;
	}
361 362

	if (strcmp(".", remote_name) != 0) {
363
		if ((error = git_remote_load(&remote, repo, remote_name)) < 0)
364 365
			goto cleanup;

366 367 368 369
		refspec = git_remote__matching_refspec(remote, merge_name);
		if (!refspec) {
			error = GIT_ENOTFOUND;
			goto cleanup;
370 371
		}

372
		if (git_refspec_transform(&buf, refspec, merge_name) < 0)
373 374 375 376 377
			goto cleanup;
	} else
		if (git_buf_sets(&buf, merge_name) < 0)
			goto cleanup;

378
	error = git_buf_set(out, git_buf_cstr(&buf), git_buf_len(&buf));
379 380

cleanup:
381
	git_config_free(config);
382 383 384 385
	git_remote_free(remote);
	git_buf_free(&buf);
	return error;
}
386

387
int git_branch_remote_name(git_buf *buf, git_repository *repo, const char *refname)
388 389
{
	git_strarray remote_list = {0};
390
	size_t i;
391 392 393 394 395
	git_remote *remote;
	const git_refspec *fetchspec;
	int error = 0;
	char *remote_name = NULL;

396 397 398
	assert(buf && repo && refname);

	git_buf_sanitize(buf);
399 400

	/* Verify that this is a remote branch */
401
	if (!git_reference__is_remote(refname)) {
402
		giterr_set(GITERR_INVALID, "Reference '%s' is not a remote branch.",
403
			refname);
404 405 406 407 408 409 410 411 412 413 414
		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++) {
		if ((error = git_remote_load(&remote, repo, remote_list.strings[i])) < 0)
415
			continue;
416

417
		fetchspec = git_remote__matching_dst_refspec(remote, refname);
418
		if (fetchspec) {
419 420 421 422 423 424 425 426
			/* 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);
427 428

				giterr_set(GITERR_REFERENCE,
429
					"Reference '%s' is ambiguous", refname);
430 431 432 433 434 435 436 437 438
				error = GIT_EAMBIGUOUS;
				goto cleanup;
			}
		}

		git_remote_free(remote);
	}

	if (remote_name) {
439 440
		git_buf_clear(buf);
		error = git_buf_puts(buf, remote_name);
441
	} else {
442
		giterr_set(GITERR_REFERENCE,
443
			"Could not determine remote for '%s'", refname);
444 445 446 447
		error = GIT_ENOTFOUND;
	}

cleanup:
448 449 450
	if (error < 0)
		git_buf_free(buf);

451 452 453 454
	git_strarray_free(&remote_list);
	return error;
}

455
int git_branch_upstream(
Jacques Germishuys committed
456 457
	git_reference **tracking_out,
	const git_reference *branch)
458 459 460 461
{
	int error;
	git_buf tracking_name = GIT_BUF_INIT;

462
	if ((error = git_branch_upstream_name(&tracking_name,
463 464 465 466 467 468 469 470 471 472 473 474
		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;
}

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
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;
	int local;
	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;
530 531 532
	else {
		giterr_set(GITERR_REFERENCE,
			"Cannot set upstream for branch '%s'", shortname);
533
		return GIT_ENOTFOUND;
534
	}
535 536 537 538 539 540 541 542 543 544

	/*
	 * 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)
		git_buf_puts(&value, ".");
	else
545
		git_branch_remote_name(&value, repo, git_reference_name(upstream));
546 547 548 549 550 551 552 553

	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) {
554 555
		git_buf_clear(&value);
		if (git_buf_puts(&value, git_reference_name(upstream)) < 0)
556 557 558 559 560 561
			goto on_error;
	} else {
		/* Get the remoe-tracking branch's refname in its repo */
		if (git_remote_load(&remote, repo, git_buf_cstr(&value)) < 0)
			goto on_error;

562
		fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream));
563
		git_buf_clear(&value);
564
		if (!fetchspec || git_refspec_rtransform(&value, fetchspec, git_reference_name(upstream)) < 0)
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
			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;
}

593
int git_branch_is_head(
594
		const git_reference *branch)
595 596 597
{
	git_reference *head;
	bool is_same = false;
598
	int error;
599 600 601 602 603 604

	assert(branch);

	if (!git_reference_is_branch(branch))
		return false;

605 606
	error = git_repository_head(&head, git_reference_owner(branch));

607
	if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
608 609 610
		return false;

	if (error < 0)
611 612 613 614 615 616 617 618 619 620
		return -1;

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

	git_reference_free(head);

	return is_same;
}