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