]> git.proxmox.com Git - libgit2.git/blame - src/branch.c
Merge pull request #2238 from libgit2/cmn/upstream-for-unborn
[libgit2.git] / src / branch.c
CommitLineData
731df570 1/*
359fc2d2 2 * Copyright (C) the libgit2 contributors. All rights reserved.
731df570 3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
8#include "common.h"
9#include "commit.h"
731df570 10#include "tag.h"
fb910281 11#include "config.h"
12#include "refspec.h"
bf031581 13#include "refs.h"
4330ab26 14#include "remote.h"
731df570 15
326ca710 16#include "git2/branch.h"
17
731df570 18static int retrieve_branch_reference(
19 git_reference **branch_reference_out,
20 git_repository *repo,
21 const char *branch_name,
22 int is_remote)
23{
3cf11eef
RB
24 git_reference *branch = NULL;
25 int error = 0;
731df570 26 char *prefix;
27 git_buf ref_name = GIT_BUF_INIT;
28
731df570 29 prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR;
30
3cf11eef
RB
31 if ((error = git_buf_joinpath(&ref_name, prefix, branch_name)) < 0)
32 /* OOM */;
33 else if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0)
34 giterr_set(
35 GITERR_REFERENCE, "Cannot locate %s branch '%s'",
36 is_remote ? "remote-tracking" : "local", branch_name);
731df570 37
3cf11eef 38 *branch_reference_out = branch; /* will be NULL on error */
731df570 39
731df570 40 git_buf_free(&ref_name);
41 return error;
42}
43
bf031581 44static int not_a_local_branch(const char *reference_name)
1c947daa 45{
bf031581 46 giterr_set(
47 GITERR_INVALID,
48 "Reference '%s' is not a local branch.", reference_name);
1c947daa
VM
49 return -1;
50}
51
731df570 52int git_branch_create(
d00d5464
ET
53 git_reference **ref_out,
54 git_repository *repository,
55 const char *branch_name,
56 const git_commit *commit,
b31ebfbc
BS
57 int force,
58 const git_signature *signature,
59 const char *log_message)
731df570 60{
59b1dbcd 61 int is_head = 0;
731df570 62 git_reference *branch = NULL;
59bb1126
BS
63 git_buf canonical_branch_name = GIT_BUF_INIT,
64 log_message_buf = GIT_BUF_INIT;
65 int error = -1;
731df570 66
cfbe4be3
VM
67 assert(branch_name && commit && ref_out);
68 assert(git_object_owner((const git_object *)commit) == repository);
a07b1698
CMN
69
70 if (force && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) {
71 error = git_branch_is_head(branch);
72 git_reference_free(branch);
73 branch = NULL;
74
75 if (error < 0)
59b1dbcd 76 goto cleanup;
a07b1698
CMN
77
78 is_head = error;
59b1dbcd 79 }
731df570 80
59b1dbcd
L
81 if (is_head && force) {
82 giterr_set(GITERR_REFERENCE, "Cannot force update branch '%s' as it is "
a07b1698
CMN
83 "the current HEAD of the repository.", branch_name);
84 error = -1;
59b1dbcd
L
85 goto cleanup;
86 }
87
b31ebfbc
BS
88 if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0)
89 goto cleanup;
731df570 90
59bb1126
BS
91 if (git_buf_sets(&log_message_buf, log_message ? log_message : "Branch: created") < 0)
92 goto cleanup;
93
94 error = git_reference_create(&branch, repository,
95 git_buf_cstr(&canonical_branch_name), git_commit_id(commit), force, signature,
96 git_buf_cstr(&log_message_buf));
97
98 if (!error)
b31ebfbc 99 *ref_out = branch;
731df570 100
b31ebfbc 101cleanup:
731df570 102 git_buf_free(&canonical_branch_name);
59bb1126 103 git_buf_free(&log_message_buf);
731df570 104 return error;
105}
106
1c947daa 107int git_branch_delete(git_reference *branch)
731df570 108{
4ba23be1 109 int is_head;
aba70781 110 git_buf config_section = GIT_BUF_INIT;
111 int error = -1;
731df570 112
1c947daa 113 assert(branch);
731df570 114
96869a4e
RB
115 if (!git_reference_is_branch(branch) && !git_reference_is_remote(branch)) {
116 giterr_set(GITERR_INVALID, "Reference '%s' is not a valid branch.",
117 git_reference_name(branch));
118 return GIT_ENOTFOUND;
1c947daa 119 }
731df570 120
4ba23be1 121 if ((is_head = git_branch_is_head(branch)) < 0)
122 return is_head;
731df570 123
4ba23be1 124 if (is_head) {
96869a4e
RB
125 giterr_set(GITERR_REFERENCE, "Cannot delete branch '%s' as it is "
126 "the current HEAD of the repository.", git_reference_name(branch));
4ba23be1 127 return -1;
731df570 128 }
129
96869a4e
RB
130 if (git_buf_join(&config_section, '.', "branch",
131 git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
aba70781 132 goto on_error;
133
134 if (git_config_rename_section(
96869a4e
RB
135 git_reference_owner(branch), git_buf_cstr(&config_section), NULL) < 0)
136 goto on_error;
0b98a8a4 137
aba70781 138 if (git_reference_delete(branch) < 0)
139 goto on_error;
140
48110f67
BS
141 if (git_reflog_delete(git_reference_owner(branch), git_reference_name(branch)) < 0)
142 goto on_error;
143
aba70781 144 error = 0;
145
146on_error:
147 git_buf_free(&config_section);
148 return error;
731df570 149}
150
8ec889a4 151typedef struct {
9bd89d96 152 git_reference_iterator *iter;
8ec889a4
CMN
153 unsigned int flags;
154} branch_iter;
155
a667ca82 156int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter)
8ec889a4
CMN
157{
158 branch_iter *iter = (branch_iter *) _iter;
56960b83 159 git_reference *ref;
8ec889a4
CMN
160 int error;
161
162 while ((error = git_reference_next(&ref, iter->iter)) == 0) {
163 if ((iter->flags & GIT_BRANCH_LOCAL) &&
164 !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) {
165 *out = ref;
166 *out_type = GIT_BRANCH_LOCAL;
167
168 return 0;
169 } else if ((iter->flags & GIT_BRANCH_REMOTE) &&
170 !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) {
171 *out = ref;
172 *out_type = GIT_BRANCH_REMOTE;
173
174 return 0;
175 } else {
176 git_reference_free(ref);
177 }
178 }
a8fd805e 179
8ec889a4
CMN
180 return error;
181}
a8fd805e 182
8ec889a4
CMN
183int git_branch_iterator_new(
184 git_branch_iterator **out,
185 git_repository *repo,
a667ca82 186 git_branch_t list_flags)
8ec889a4
CMN
187{
188 branch_iter *iter;
9bd89d96 189
8ec889a4
CMN
190 iter = git__calloc(1, sizeof(branch_iter));
191 GITERR_CHECK_ALLOC(iter);
56960b83 192
8ec889a4 193 iter->flags = list_flags;
09c2f91c 194
8ec889a4
CMN
195 if (git_reference_iterator_new(&iter->iter, repo) < 0) {
196 git__free(iter);
197 return -1;
9bd89d96
CMN
198 }
199
8ec889a4 200 *out = (git_branch_iterator *) iter;
9bd89d96 201
8ec889a4
CMN
202 return 0;
203}
204
205void git_branch_iterator_free(git_branch_iterator *_iter)
206{
207 branch_iter *iter = (branch_iter *) _iter;
208
9eb45fc5
BR
209 if (iter == NULL)
210 return;
211
8ec889a4
CMN
212 git_reference_iterator_free(iter->iter);
213 git__free(iter);
a8fd805e 214}
215
bf9e8cc8 216int git_branch_move(
d00d5464 217 git_reference **out,
bf9e8cc8 218 git_reference *branch,
219 const char *new_branch_name,
540c1809
BS
220 int force,
221 const git_signature *signature,
222 const char *log_message)
bf9e8cc8 223{
383f164a 224 git_buf new_reference_name = GIT_BUF_INIT,
59bb1126
BS
225 old_config_section = GIT_BUF_INIT,
226 new_config_section = GIT_BUF_INIT,
227 log_message_buf = GIT_BUF_INIT;
bf9e8cc8 228 int error;
6a8bcfa4 229
bf9e8cc8 230 assert(branch && new_branch_name);
231
232 if (!git_reference_is_branch(branch))
bf031581 233 return not_a_local_branch(git_reference_name(branch));
4615f0f7 234
59bb1126 235 if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
d00d5464 236 goto done;
383f164a 237
59bb1126
BS
238 if (log_message) {
239 if ((error = git_buf_sets(&log_message_buf, log_message)) < 0)
240 goto done;
241 } else {
242 if ((error = git_buf_printf(&log_message_buf, "Branch: renamed %s to %s",
243 git_reference_name(branch), git_buf_cstr(&new_reference_name))) < 0)
244 goto done;
245 }
246
96869a4e 247 /* first update ref then config so failure won't trash config */
4e6e2ff2 248
96869a4e 249 error = git_reference_rename(
ccf6ce5c 250 out, branch, git_buf_cstr(&new_reference_name), force,
59bb1126 251 signature, git_buf_cstr(&log_message_buf));
96869a4e 252 if (error < 0)
d00d5464 253 goto done;
3e199f42 254
96869a4e
RB
255 git_buf_join(&old_config_section, '.', "branch",
256 git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR));
257 git_buf_join(&new_config_section, '.', "branch", new_branch_name);
258
259 error = git_config_rename_section(
260 git_reference_owner(branch),
261 git_buf_cstr(&old_config_section),
262 git_buf_cstr(&new_config_section));
4615f0f7 263
d00d5464 264done:
6a8bcfa4 265 git_buf_free(&new_reference_name);
aba70781 266 git_buf_free(&old_config_section);
267 git_buf_free(&new_config_section);
59bb1126 268 git_buf_free(&log_message_buf);
6a8bcfa4 269
6a625435 270 return error;
4615f0f7 271}
eed378b6 272
273int git_branch_lookup(
d00d5464
ET
274 git_reference **ref_out,
275 git_repository *repo,
276 const char *branch_name,
277 git_branch_t branch_type)
eed378b6 278{
279 assert(ref_out && repo && branch_name);
280
281 return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE);
282}
fb910281 283
c253056d
SB
284int git_branch_name(const char **out, git_reference *ref)
285{
286 const char *branch_name;
287
288 assert(out && ref);
289
290 branch_name = ref->name;
291
292 if (git_reference_is_branch(ref)) {
293 branch_name += strlen(GIT_REFS_HEADS_DIR);
294 } else if (git_reference_is_remote(ref)) {
295 branch_name += strlen(GIT_REFS_REMOTES_DIR);
296 } else {
297 giterr_set(GITERR_INVALID,
298 "Reference '%s' is neither a local nor a remote branch.", ref->name);
299 return -1;
300 }
301 *out = branch_name;
302 return 0;
303}
304
a258d8e3 305static int retrieve_upstream_configuration(
bf031581 306 const char **out,
307 git_repository *repo,
308 const char *canonical_branch_name,
309 const char *format)
fb910281 310{
311 git_config *config;
312 git_buf buf = GIT_BUF_INIT;
313 int error;
314
bf031581 315 if (git_repository_config__weakptr(&config, repo) < 0)
fb910281 316 return -1;
317
318 if (git_buf_printf(&buf, format,
bf031581 319 canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0)
fb910281 320 return -1;
321
322 error = git_config_get_string(out, config, git_buf_cstr(&buf));
323 git_buf_free(&buf);
324 return error;
325}
326
b25d87c9
CMN
327int git_branch_upstream_name(
328 git_buf *out,
bf031581 329 git_repository *repo,
b25d87c9 330 const char *refname)
fb910281 331{
332 const char *remote_name, *merge_name;
333 git_buf buf = GIT_BUF_INIT;
334 int error = -1;
335 git_remote *remote = NULL;
336 const git_refspec *refspec;
337
b25d87c9
CMN
338 assert(out && refname);
339
340 git_buf_sanitize(out);
fb910281 341
b25d87c9
CMN
342 if (!git_reference__is_branch(refname))
343 return not_a_local_branch(refname);
fb910281 344
a258d8e3 345 if ((error = retrieve_upstream_configuration(
b25d87c9 346 &remote_name, repo, refname, "branch.%s.remote")) < 0)
bf031581 347 goto cleanup;
fb910281 348
a258d8e3 349 if ((error = retrieve_upstream_configuration(
b25d87c9 350 &merge_name, repo, refname, "branch.%s.merge")) < 0)
bf031581 351 goto cleanup;
37849a8e 352
28cbd2e2 353 if (!*remote_name || !*merge_name) {
3e199f42 354 giterr_set(GITERR_REFERENCE,
b25d87c9 355 "branch '%s' does not have an upstream", refname);
28cbd2e2 356 error = GIT_ENOTFOUND;
357 goto cleanup;
358 }
fb910281 359
360 if (strcmp(".", remote_name) != 0) {
bf031581 361 if ((error = git_remote_load(&remote, repo, remote_name)) < 0)
fb910281 362 goto cleanup;
363
4330ab26
CMN
364 refspec = git_remote__matching_refspec(remote, merge_name);
365 if (!refspec) {
366 error = GIT_ENOTFOUND;
367 goto cleanup;
fb910281 368 }
369
bf522e08 370 if (git_refspec_transform(&buf, refspec, merge_name) < 0)
fb910281 371 goto cleanup;
372 } else
373 if (git_buf_sets(&buf, merge_name) < 0)
374 goto cleanup;
375
b25d87c9 376 error = git_buf_set(out, git_buf_cstr(&buf), git_buf_len(&buf));
fb910281 377
378cleanup:
379 git_remote_free(remote);
380 git_buf_free(&buf);
381 return error;
382}
0c78f685 383
b25d87c9 384int git_branch_remote_name(git_buf *buf, git_repository *repo, const char *refname)
2e3e8c88
JM
385{
386 git_strarray remote_list = {0};
97016f29 387 size_t i;
2e3e8c88
JM
388 git_remote *remote;
389 const git_refspec *fetchspec;
390 int error = 0;
391 char *remote_name = NULL;
392
b25d87c9
CMN
393 assert(buf && repo && refname);
394
395 git_buf_sanitize(buf);
2e3e8c88
JM
396
397 /* Verify that this is a remote branch */
b25d87c9 398 if (!git_reference__is_remote(refname)) {
c1b5e8c4 399 giterr_set(GITERR_INVALID, "Reference '%s' is not a remote branch.",
b25d87c9 400 refname);
2e3e8c88
JM
401 error = GIT_ERROR;
402 goto cleanup;
403 }
404
405 /* Get the remotes */
406 if ((error = git_remote_list(&remote_list, repo)) < 0)
407 goto cleanup;
408
409 /* Find matching remotes */
410 for (i = 0; i < remote_list.count; i++) {
411 if ((error = git_remote_load(&remote, repo, remote_list.strings[i])) < 0)
d59942c2 412 continue;
2e3e8c88 413
b25d87c9 414 fetchspec = git_remote__matching_dst_refspec(remote, refname);
4330ab26 415 if (fetchspec) {
2e3e8c88
JM
416 /* If we have not already set out yet, then set
417 * it to the matching remote name. Otherwise
418 * multiple remotes match this reference, and it
419 * is ambiguous. */
420 if (!remote_name) {
421 remote_name = remote_list.strings[i];
422 } else {
423 git_remote_free(remote);
3e199f42
RB
424
425 giterr_set(GITERR_REFERENCE,
b25d87c9 426 "Reference '%s' is ambiguous", refname);
2e3e8c88
JM
427 error = GIT_EAMBIGUOUS;
428 goto cleanup;
429 }
430 }
431
432 git_remote_free(remote);
433 }
434
435 if (remote_name) {
97016f29
CMN
436 git_buf_clear(buf);
437 error = git_buf_puts(buf, remote_name);
2e3e8c88 438 } else {
3e199f42 439 giterr_set(GITERR_REFERENCE,
b25d87c9 440 "Could not determine remote for '%s'", refname);
2e3e8c88 441 error = GIT_ENOTFOUND;
2e3e8c88
JM
442 }
443
444cleanup:
b25d87c9
CMN
445 if (error < 0)
446 git_buf_free(buf);
447
2e3e8c88
JM
448 git_strarray_free(&remote_list);
449 return error;
450}
451
a258d8e3 452int git_branch_upstream(
bf031581 453 git_reference **tracking_out,
454 git_reference *branch)
455{
456 int error;
457 git_buf tracking_name = GIT_BUF_INIT;
458
b25d87c9 459 if ((error = git_branch_upstream_name(&tracking_name,
bf031581 460 git_reference_owner(branch), git_reference_name(branch))) < 0)
461 return error;
462
463 error = git_reference_lookup(
464 tracking_out,
465 git_reference_owner(branch),
466 git_buf_cstr(&tracking_name));
467
468 git_buf_free(&tracking_name);
469 return error;
470}
471
d59942c2
CMN
472static int unset_upstream(git_config *config, const char *shortname)
473{
474 git_buf buf = GIT_BUF_INIT;
475
476 if (git_buf_printf(&buf, "branch.%s.remote", shortname) < 0)
477 return -1;
478
479 if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
480 goto on_error;
481
482 git_buf_clear(&buf);
483 if (git_buf_printf(&buf, "branch.%s.merge", shortname) < 0)
484 goto on_error;
485
486 if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
487 goto on_error;
488
489 git_buf_free(&buf);
490 return 0;
491
492on_error:
493 git_buf_free(&buf);
494 return -1;
495}
496
497int git_branch_set_upstream(git_reference *branch, const char *upstream_name)
498{
499 git_buf key = GIT_BUF_INIT, value = GIT_BUF_INIT;
500 git_reference *upstream;
501 git_repository *repo;
502 git_remote *remote = NULL;
503 git_config *config;
504 const char *name, *shortname;
505 int local;
506 const git_refspec *fetchspec;
507
508 name = git_reference_name(branch);
509 if (!git_reference__is_branch(name))
510 return not_a_local_branch(name);
511
512 if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0)
513 return -1;
514
515 shortname = name + strlen(GIT_REFS_HEADS_DIR);
516
517 if (upstream_name == NULL)
518 return unset_upstream(config, shortname);
519
520 repo = git_reference_owner(branch);
521
522 /* First we need to figure out whether it's a branch or remote-tracking */
523 if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_LOCAL) == 0)
524 local = 1;
525 else if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_REMOTE) == 0)
526 local = 0;
3e199f42
RB
527 else {
528 giterr_set(GITERR_REFERENCE,
529 "Cannot set upstream for branch '%s'", shortname);
d59942c2 530 return GIT_ENOTFOUND;
3e199f42 531 }
d59942c2
CMN
532
533 /*
534 * If it's local, the remote is "." and the branch name is
535 * simply the refname. Otherwise we need to figure out what
536 * the remote-tracking branch's name on the remote is and use
537 * that.
538 */
539 if (local)
540 git_buf_puts(&value, ".");
541 else
b25d87c9 542 git_branch_remote_name(&value, repo, git_reference_name(upstream));
d59942c2
CMN
543
544 if (git_buf_printf(&key, "branch.%s.remote", shortname) < 0)
545 goto on_error;
546
547 if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
548 goto on_error;
549
550 if (local) {
3d42e9a3
NV
551 git_buf_clear(&value);
552 if (git_buf_puts(&value, git_reference_name(upstream)) < 0)
d59942c2
CMN
553 goto on_error;
554 } else {
555 /* Get the remoe-tracking branch's refname in its repo */
556 if (git_remote_load(&remote, repo, git_buf_cstr(&value)) < 0)
557 goto on_error;
558
4330ab26 559 fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream));
d59942c2 560 git_buf_clear(&value);
bf522e08 561 if (!fetchspec || git_refspec_rtransform(&value, fetchspec, git_reference_name(upstream)) < 0)
d59942c2
CMN
562 goto on_error;
563
564 git_remote_free(remote);
565 remote = NULL;
566 }
567
568 git_buf_clear(&key);
569 if (git_buf_printf(&key, "branch.%s.merge", shortname) < 0)
570 goto on_error;
571
572 if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
573 goto on_error;
574
575 git_reference_free(upstream);
576 git_buf_free(&key);
577 git_buf_free(&value);
578
579 return 0;
580
581on_error:
582 git_reference_free(upstream);
583 git_buf_free(&key);
584 git_buf_free(&value);
585 git_remote_free(remote);
586
587 return -1;
588}
589
0c78f685 590int git_branch_is_head(
853b1407 591 const git_reference *branch)
0c78f685 592{
593 git_reference *head;
594 bool is_same = false;
0532e7bb 595 int error;
0c78f685 596
597 assert(branch);
598
599 if (!git_reference_is_branch(branch))
600 return false;
601
0532e7bb 602 error = git_repository_head(&head, git_reference_owner(branch));
603
605da51a 604 if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
0532e7bb 605 return false;
606
607 if (error < 0)
0c78f685 608 return -1;
609
610 is_same = strcmp(
611 git_reference_name(branch),
612 git_reference_name(head)) == 0;
613
614 git_reference_free(head);
615
616 return is_same;
617}