]>
Commit | Line | Data |
---|---|---|
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 | ||
eae0bfdc PP |
8 | #include "branch.h" |
9 | ||
731df570 | 10 | #include "commit.h" |
731df570 | 11 | #include "tag.h" |
fb910281 | 12 | #include "config.h" |
13 | #include "refspec.h" | |
bf031581 | 14 | #include "refs.h" |
4330ab26 | 15 | #include "remote.h" |
62d38a1d | 16 | #include "annotated_commit.h" |
e3acd37b | 17 | #include "worktree.h" |
731df570 | 18 | |
326ca710 | 19 | #include "git2/branch.h" |
20 | ||
731df570 | 21 | static int retrieve_branch_reference( |
22 | git_reference **branch_reference_out, | |
23 | git_repository *repo, | |
24 | const char *branch_name, | |
22a2d3d5 | 25 | bool is_remote) |
731df570 | 26 | { |
3cf11eef RB |
27 | git_reference *branch = NULL; |
28 | int error = 0; | |
731df570 | 29 | char *prefix; |
30 | git_buf ref_name = GIT_BUF_INIT; | |
31 | ||
731df570 | 32 | prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR; |
33 | ||
3cf11eef RB |
34 | if ((error = git_buf_joinpath(&ref_name, prefix, branch_name)) < 0) |
35 | /* OOM */; | |
36 | else if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0) | |
ac3d33df JK |
37 | git_error_set( |
38 | GIT_ERROR_REFERENCE, "cannot locate %s branch '%s'", | |
3cf11eef | 39 | is_remote ? "remote-tracking" : "local", branch_name); |
731df570 | 40 | |
3cf11eef | 41 | *branch_reference_out = branch; /* will be NULL on error */ |
731df570 | 42 | |
ac3d33df | 43 | git_buf_dispose(&ref_name); |
731df570 | 44 | return error; |
45 | } | |
46 | ||
bf031581 | 47 | static int not_a_local_branch(const char *reference_name) |
1c947daa | 48 | { |
ac3d33df JK |
49 | git_error_set( |
50 | GIT_ERROR_INVALID, | |
909d5494 | 51 | "reference '%s' is not a local branch.", reference_name); |
1c947daa VM |
52 | return -1; |
53 | } | |
54 | ||
62d38a1d | 55 | static int create_branch( |
d00d5464 ET |
56 | git_reference **ref_out, |
57 | git_repository *repository, | |
58 | const char *branch_name, | |
59 | const git_commit *commit, | |
62d38a1d | 60 | const char *from, |
6bfb990d | 61 | int force) |
731df570 | 62 | { |
f9793884 | 63 | int is_unmovable_head = 0; |
731df570 | 64 | git_reference *branch = NULL; |
59bb1126 | 65 | git_buf canonical_branch_name = GIT_BUF_INIT, |
6bfb990d | 66 | log_message = GIT_BUF_INIT; |
59bb1126 | 67 | int error = -1; |
f9793884 | 68 | int bare = git_repository_is_bare(repository); |
731df570 | 69 | |
c25aa7cd PP |
70 | GIT_ASSERT_ARG(branch_name); |
71 | GIT_ASSERT_ARG(commit); | |
72 | GIT_ASSERT_ARG(ref_out); | |
73 | GIT_ASSERT_ARG(git_commit_owner(commit) == repository); | |
a07b1698 | 74 | |
eae0bfdc | 75 | if (!git__strcmp(branch_name, "HEAD")) { |
ac3d33df | 76 | git_error_set(GIT_ERROR_REFERENCE, "'HEAD' is not a valid branch name"); |
eae0bfdc PP |
77 | error = -1; |
78 | goto cleanup; | |
79 | } | |
80 | ||
f9793884 | 81 | if (force && !bare && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) { |
a07b1698 CMN |
82 | error = git_branch_is_head(branch); |
83 | git_reference_free(branch); | |
84 | branch = NULL; | |
85 | ||
86 | if (error < 0) | |
59b1dbcd | 87 | goto cleanup; |
a07b1698 | 88 | |
f9793884 | 89 | is_unmovable_head = error; |
59b1dbcd | 90 | } |
731df570 | 91 | |
f9793884 | 92 | if (is_unmovable_head && force) { |
ac3d33df | 93 | git_error_set(GIT_ERROR_REFERENCE, "cannot force update branch '%s' as it is " |
a07b1698 CMN |
94 | "the current HEAD of the repository.", branch_name); |
95 | error = -1; | |
59b1dbcd L |
96 | goto cleanup; |
97 | } | |
05e644dd | 98 | |
b31ebfbc BS |
99 | if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) |
100 | goto cleanup; | |
731df570 | 101 | |
62d38a1d | 102 | if (git_buf_printf(&log_message, "branch: Created from %s", from) < 0) |
59bb1126 BS |
103 | goto cleanup; |
104 | ||
105 | error = git_reference_create(&branch, repository, | |
659cf202 | 106 | git_buf_cstr(&canonical_branch_name), git_commit_id(commit), force, |
6bfb990d | 107 | git_buf_cstr(&log_message)); |
59bb1126 BS |
108 | |
109 | if (!error) | |
b31ebfbc | 110 | *ref_out = branch; |
731df570 | 111 | |
b31ebfbc | 112 | cleanup: |
ac3d33df JK |
113 | git_buf_dispose(&canonical_branch_name); |
114 | git_buf_dispose(&log_message); | |
731df570 | 115 | return error; |
116 | } | |
117 | ||
62d38a1d CMN |
118 | int git_branch_create( |
119 | git_reference **ref_out, | |
120 | git_repository *repository, | |
121 | const char *branch_name, | |
122 | const git_commit *commit, | |
123 | int force) | |
124 | { | |
125 | return create_branch(ref_out, repository, branch_name, commit, git_oid_tostr_s(git_commit_id(commit)), force); | |
126 | } | |
127 | ||
128 | int git_branch_create_from_annotated( | |
129 | git_reference **ref_out, | |
130 | git_repository *repository, | |
131 | const char *branch_name, | |
132 | const git_annotated_commit *commit, | |
133 | int force) | |
134 | { | |
d5592378 ET |
135 | return create_branch(ref_out, |
136 | repository, branch_name, commit->commit, commit->description, force); | |
62d38a1d CMN |
137 | } |
138 | ||
22a2d3d5 | 139 | static int branch_is_checked_out(git_repository *worktree, void *payload) |
e3acd37b | 140 | { |
38fc5ab0 | 141 | git_reference *branch = (git_reference *) payload; |
be343b88 | 142 | git_reference *head = NULL; |
22a2d3d5 | 143 | int error; |
e3acd37b | 144 | |
22a2d3d5 UG |
145 | if (git_repository_is_bare(worktree)) |
146 | return 0; | |
e3acd37b | 147 | |
22a2d3d5 UG |
148 | if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0) { |
149 | if (error == GIT_ENOTFOUND) | |
150 | error = 0; | |
151 | goto out; | |
152 | } | |
be343b88 | 153 | |
22a2d3d5 UG |
154 | if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC) |
155 | goto out; | |
156 | ||
157 | error = !git__strcmp(head->target.symbolic, branch->name); | |
158 | ||
159 | out: | |
38fc5ab0 | 160 | git_reference_free(head); |
22a2d3d5 | 161 | return error; |
38fc5ab0 | 162 | } |
e3acd37b | 163 | |
38fc5ab0 PS |
164 | int git_branch_is_checked_out(const git_reference *branch) |
165 | { | |
c25aa7cd PP |
166 | GIT_ASSERT_ARG(branch); |
167 | ||
22a2d3d5 UG |
168 | if (!git_reference_is_branch(branch)) |
169 | return 0; | |
170 | return git_repository_foreach_worktree(git_reference_owner(branch), | |
171 | branch_is_checked_out, (void *)branch) == 1; | |
e3acd37b PS |
172 | } |
173 | ||
1c947daa | 174 | int git_branch_delete(git_reference *branch) |
731df570 | 175 | { |
4ba23be1 | 176 | int is_head; |
aba70781 | 177 | git_buf config_section = GIT_BUF_INIT; |
178 | int error = -1; | |
731df570 | 179 | |
c25aa7cd | 180 | GIT_ASSERT_ARG(branch); |
731df570 | 181 | |
96869a4e | 182 | if (!git_reference_is_branch(branch) && !git_reference_is_remote(branch)) { |
ac3d33df | 183 | git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a valid branch.", |
96869a4e RB |
184 | git_reference_name(branch)); |
185 | return GIT_ENOTFOUND; | |
1c947daa | 186 | } |
731df570 | 187 | |
4ba23be1 | 188 | if ((is_head = git_branch_is_head(branch)) < 0) |
189 | return is_head; | |
731df570 | 190 | |
4ba23be1 | 191 | if (is_head) { |
ac3d33df | 192 | git_error_set(GIT_ERROR_REFERENCE, "cannot delete branch '%s' as it is " |
96869a4e | 193 | "the current HEAD of the repository.", git_reference_name(branch)); |
4ba23be1 | 194 | return -1; |
731df570 | 195 | } |
196 | ||
143e539f | 197 | if (git_reference_is_branch(branch) && git_branch_is_checked_out(branch)) { |
ac3d33df | 198 | git_error_set(GIT_ERROR_REFERENCE, "Cannot delete branch '%s' as it is " |
143e539f PS |
199 | "the current HEAD of a linked repository.", git_reference_name(branch)); |
200 | return -1; | |
201 | } | |
202 | ||
96869a4e RB |
203 | if (git_buf_join(&config_section, '.', "branch", |
204 | git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) | |
aba70781 | 205 | goto on_error; |
206 | ||
207 | if (git_config_rename_section( | |
96869a4e RB |
208 | git_reference_owner(branch), git_buf_cstr(&config_section), NULL) < 0) |
209 | goto on_error; | |
0b98a8a4 | 210 | |
01d0c02d | 211 | error = git_reference_delete(branch); |
aba70781 | 212 | |
213 | on_error: | |
ac3d33df | 214 | git_buf_dispose(&config_section); |
aba70781 | 215 | return error; |
731df570 | 216 | } |
217 | ||
8ec889a4 | 218 | typedef struct { |
9bd89d96 | 219 | git_reference_iterator *iter; |
8ec889a4 CMN |
220 | unsigned int flags; |
221 | } branch_iter; | |
222 | ||
a667ca82 | 223 | int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter) |
8ec889a4 CMN |
224 | { |
225 | branch_iter *iter = (branch_iter *) _iter; | |
56960b83 | 226 | git_reference *ref; |
8ec889a4 CMN |
227 | int error; |
228 | ||
229 | while ((error = git_reference_next(&ref, iter->iter)) == 0) { | |
230 | if ((iter->flags & GIT_BRANCH_LOCAL) && | |
231 | !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) { | |
232 | *out = ref; | |
233 | *out_type = GIT_BRANCH_LOCAL; | |
234 | ||
235 | return 0; | |
236 | } else if ((iter->flags & GIT_BRANCH_REMOTE) && | |
237 | !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) { | |
238 | *out = ref; | |
239 | *out_type = GIT_BRANCH_REMOTE; | |
240 | ||
241 | return 0; | |
242 | } else { | |
243 | git_reference_free(ref); | |
244 | } | |
245 | } | |
a8fd805e | 246 | |
8ec889a4 CMN |
247 | return error; |
248 | } | |
a8fd805e | 249 | |
8ec889a4 CMN |
250 | int git_branch_iterator_new( |
251 | git_branch_iterator **out, | |
252 | git_repository *repo, | |
a667ca82 | 253 | git_branch_t list_flags) |
8ec889a4 CMN |
254 | { |
255 | branch_iter *iter; | |
9bd89d96 | 256 | |
8ec889a4 | 257 | iter = git__calloc(1, sizeof(branch_iter)); |
ac3d33df | 258 | GIT_ERROR_CHECK_ALLOC(iter); |
56960b83 | 259 | |
8ec889a4 | 260 | iter->flags = list_flags; |
09c2f91c | 261 | |
8ec889a4 CMN |
262 | if (git_reference_iterator_new(&iter->iter, repo) < 0) { |
263 | git__free(iter); | |
264 | return -1; | |
9bd89d96 CMN |
265 | } |
266 | ||
8ec889a4 | 267 | *out = (git_branch_iterator *) iter; |
9bd89d96 | 268 | |
8ec889a4 CMN |
269 | return 0; |
270 | } | |
271 | ||
272 | void git_branch_iterator_free(git_branch_iterator *_iter) | |
273 | { | |
274 | branch_iter *iter = (branch_iter *) _iter; | |
275 | ||
9eb45fc5 BR |
276 | if (iter == NULL) |
277 | return; | |
278 | ||
8ec889a4 CMN |
279 | git_reference_iterator_free(iter->iter); |
280 | git__free(iter); | |
a8fd805e | 281 | } |
282 | ||
bf9e8cc8 | 283 | int git_branch_move( |
d00d5464 | 284 | git_reference **out, |
bf9e8cc8 | 285 | git_reference *branch, |
286 | const char *new_branch_name, | |
6bfb990d | 287 | int force) |
bf9e8cc8 | 288 | { |
383f164a | 289 | git_buf new_reference_name = GIT_BUF_INIT, |
59bb1126 BS |
290 | old_config_section = GIT_BUF_INIT, |
291 | new_config_section = GIT_BUF_INIT, | |
6bfb990d | 292 | log_message = GIT_BUF_INIT; |
bf9e8cc8 | 293 | int error; |
6a8bcfa4 | 294 | |
c25aa7cd PP |
295 | GIT_ASSERT_ARG(branch); |
296 | GIT_ASSERT_ARG(new_branch_name); | |
bf9e8cc8 | 297 | |
298 | if (!git_reference_is_branch(branch)) | |
bf031581 | 299 | return not_a_local_branch(git_reference_name(branch)); |
4615f0f7 | 300 | |
59bb1126 | 301 | if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) |
d00d5464 | 302 | goto done; |
383f164a | 303 | |
7eb76734 | 304 | if ((error = git_buf_printf(&log_message, "branch: renamed %s to %s", |
6bfb990d | 305 | git_reference_name(branch), git_buf_cstr(&new_reference_name))) < 0) |
59bb1126 | 306 | goto done; |
59bb1126 | 307 | |
96869a4e | 308 | /* first update ref then config so failure won't trash config */ |
4e6e2ff2 | 309 | |
96869a4e | 310 | error = git_reference_rename( |
ccf6ce5c | 311 | out, branch, git_buf_cstr(&new_reference_name), force, |
6bfb990d | 312 | git_buf_cstr(&log_message)); |
96869a4e | 313 | if (error < 0) |
d00d5464 | 314 | goto done; |
3e199f42 | 315 | |
96869a4e RB |
316 | git_buf_join(&old_config_section, '.', "branch", |
317 | git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)); | |
318 | git_buf_join(&new_config_section, '.', "branch", new_branch_name); | |
319 | ||
320 | error = git_config_rename_section( | |
321 | git_reference_owner(branch), | |
322 | git_buf_cstr(&old_config_section), | |
323 | git_buf_cstr(&new_config_section)); | |
4615f0f7 | 324 | |
d00d5464 | 325 | done: |
ac3d33df JK |
326 | git_buf_dispose(&new_reference_name); |
327 | git_buf_dispose(&old_config_section); | |
328 | git_buf_dispose(&new_config_section); | |
329 | git_buf_dispose(&log_message); | |
6a8bcfa4 | 330 | |
6a625435 | 331 | return error; |
4615f0f7 | 332 | } |
eed378b6 | 333 | |
334 | int git_branch_lookup( | |
d00d5464 ET |
335 | git_reference **ref_out, |
336 | git_repository *repo, | |
337 | const char *branch_name, | |
338 | git_branch_t branch_type) | |
eed378b6 | 339 | { |
22a2d3d5 | 340 | int error = -1; |
c25aa7cd PP |
341 | |
342 | GIT_ASSERT_ARG(ref_out); | |
343 | GIT_ASSERT_ARG(repo); | |
344 | GIT_ASSERT_ARG(branch_name); | |
eed378b6 | 345 | |
22a2d3d5 UG |
346 | switch (branch_type) { |
347 | case GIT_BRANCH_LOCAL: | |
348 | case GIT_BRANCH_REMOTE: | |
349 | error = retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE); | |
350 | break; | |
351 | case GIT_BRANCH_ALL: | |
352 | error = retrieve_branch_reference(ref_out, repo, branch_name, false); | |
353 | if (error == GIT_ENOTFOUND) | |
354 | error = retrieve_branch_reference(ref_out, repo, branch_name, true); | |
355 | break; | |
356 | default: | |
c25aa7cd | 357 | GIT_ASSERT(false); |
22a2d3d5 UG |
358 | } |
359 | return error; | |
eed378b6 | 360 | } |
fb910281 | 361 | |
3b4ba278 JG |
362 | int git_branch_name( |
363 | const char **out, | |
364 | const git_reference *ref) | |
c253056d SB |
365 | { |
366 | const char *branch_name; | |
367 | ||
c25aa7cd PP |
368 | GIT_ASSERT_ARG(out); |
369 | GIT_ASSERT_ARG(ref); | |
c253056d SB |
370 | |
371 | branch_name = ref->name; | |
372 | ||
373 | if (git_reference_is_branch(ref)) { | |
374 | branch_name += strlen(GIT_REFS_HEADS_DIR); | |
375 | } else if (git_reference_is_remote(ref)) { | |
376 | branch_name += strlen(GIT_REFS_REMOTES_DIR); | |
377 | } else { | |
ac3d33df | 378 | git_error_set(GIT_ERROR_INVALID, |
909d5494 | 379 | "reference '%s' is neither a local nor a remote branch.", ref->name); |
c253056d SB |
380 | return -1; |
381 | } | |
382 | *out = branch_name; | |
383 | return 0; | |
384 | } | |
385 | ||
a258d8e3 | 386 | static int retrieve_upstream_configuration( |
9a97f49e | 387 | git_buf *out, |
29c4cb09 | 388 | const git_config *config, |
bf031581 | 389 | const char *canonical_branch_name, |
390 | const char *format) | |
fb910281 | 391 | { |
fb910281 | 392 | git_buf buf = GIT_BUF_INIT; |
393 | int error; | |
394 | ||
fb910281 | 395 | if (git_buf_printf(&buf, format, |
bf031581 | 396 | canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0) |
fb910281 | 397 | return -1; |
398 | ||
9a97f49e | 399 | error = git_config_get_string_buf(out, config, git_buf_cstr(&buf)); |
ac3d33df | 400 | git_buf_dispose(&buf); |
fb910281 | 401 | return error; |
402 | } | |
403 | ||
b25d87c9 CMN |
404 | int git_branch_upstream_name( |
405 | git_buf *out, | |
bf031581 | 406 | git_repository *repo, |
b25d87c9 | 407 | const char *refname) |
fb910281 | 408 | { |
9a97f49e CMN |
409 | git_buf remote_name = GIT_BUF_INIT; |
410 | git_buf merge_name = GIT_BUF_INIT; | |
fb910281 | 411 | git_buf buf = GIT_BUF_INIT; |
412 | int error = -1; | |
413 | git_remote *remote = NULL; | |
414 | const git_refspec *refspec; | |
ac99d86b | 415 | git_config *config; |
fb910281 | 416 | |
c25aa7cd PP |
417 | GIT_ASSERT_ARG(out); |
418 | GIT_ASSERT_ARG(refname); | |
b25d87c9 | 419 | |
c25aa7cd PP |
420 | if ((error = git_buf_sanitize(out)) < 0) |
421 | return error; | |
fb910281 | 422 | |
b25d87c9 CMN |
423 | if (!git_reference__is_branch(refname)) |
424 | return not_a_local_branch(refname); | |
fb910281 | 425 | |
ac99d86b | 426 | if ((error = git_repository_config_snapshot(&config, repo)) < 0) |
29c4cb09 CMN |
427 | return error; |
428 | ||
a258d8e3 | 429 | if ((error = retrieve_upstream_configuration( |
29c4cb09 | 430 | &remote_name, config, refname, "branch.%s.remote")) < 0) |
bf031581 | 431 | goto cleanup; |
fb910281 | 432 | |
a258d8e3 | 433 | if ((error = retrieve_upstream_configuration( |
29c4cb09 | 434 | &merge_name, config, refname, "branch.%s.merge")) < 0) |
bf031581 | 435 | goto cleanup; |
37849a8e | 436 | |
9a97f49e | 437 | if (git_buf_len(&remote_name) == 0 || git_buf_len(&merge_name) == 0) { |
ac3d33df | 438 | git_error_set(GIT_ERROR_REFERENCE, |
b25d87c9 | 439 | "branch '%s' does not have an upstream", refname); |
28cbd2e2 | 440 | error = GIT_ENOTFOUND; |
441 | goto cleanup; | |
442 | } | |
fb910281 | 443 | |
9a97f49e CMN |
444 | if (strcmp(".", git_buf_cstr(&remote_name)) != 0) { |
445 | if ((error = git_remote_lookup(&remote, repo, git_buf_cstr(&remote_name))) < 0) | |
fb910281 | 446 | goto cleanup; |
447 | ||
9a97f49e | 448 | refspec = git_remote__matching_refspec(remote, git_buf_cstr(&merge_name)); |
4330ab26 CMN |
449 | if (!refspec) { |
450 | error = GIT_ENOTFOUND; | |
451 | goto cleanup; | |
fb910281 | 452 | } |
453 | ||
9a97f49e | 454 | if (git_refspec_transform(&buf, refspec, git_buf_cstr(&merge_name)) < 0) |
fb910281 | 455 | goto cleanup; |
456 | } else | |
9a97f49e | 457 | if (git_buf_set(&buf, git_buf_cstr(&merge_name), git_buf_len(&merge_name)) < 0) |
fb910281 | 458 | goto cleanup; |
459 | ||
b25d87c9 | 460 | error = git_buf_set(out, git_buf_cstr(&buf), git_buf_len(&buf)); |
fb910281 | 461 | |
462 | cleanup: | |
29c4cb09 | 463 | git_config_free(config); |
fb910281 | 464 | git_remote_free(remote); |
ac3d33df JK |
465 | git_buf_dispose(&remote_name); |
466 | git_buf_dispose(&merge_name); | |
467 | git_buf_dispose(&buf); | |
fb910281 | 468 | return error; |
469 | } | |
0c78f685 | 470 | |
c25aa7cd | 471 | static int git_branch_upstream_with_format(git_buf *buf, git_repository *repo, const char *refname, const char *format, const char *format_name) |
82374d98 CMN |
472 | { |
473 | int error; | |
82374d98 CMN |
474 | git_config *cfg; |
475 | ||
476 | if (!git_reference__is_branch(refname)) | |
477 | return not_a_local_branch(refname); | |
478 | ||
9a97f49e | 479 | if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) |
82374d98 CMN |
480 | return error; |
481 | ||
c25aa7cd PP |
482 | if ((error = git_buf_sanitize(buf)) < 0 || |
483 | (error = retrieve_upstream_configuration(buf, cfg, refname, format)) < 0) | |
9a97f49e CMN |
484 | return error; |
485 | ||
486 | if (git_buf_len(buf) == 0) { | |
c25aa7cd | 487 | git_error_set(GIT_ERROR_REFERENCE, "branch '%s' does not have an upstream %s", refname, format_name); |
5915d700 | 488 | error = GIT_ENOTFOUND; |
9a97f49e | 489 | git_buf_clear(buf); |
5915d700 CMN |
490 | } |
491 | ||
82374d98 CMN |
492 | return error; |
493 | } | |
494 | ||
c25aa7cd PP |
495 | int git_branch_upstream_remote(git_buf *buf, git_repository *repo, const char *refname) |
496 | { | |
497 | return git_branch_upstream_with_format(buf, repo, refname, "branch.%s.remote", "remote"); | |
498 | } | |
499 | ||
500 | int git_branch_upstream_merge(git_buf *buf, git_repository *repo, const char *refname) | |
501 | { | |
502 | return git_branch_upstream_with_format(buf, repo, refname, "branch.%s.merge", "merge"); | |
503 | } | |
504 | ||
b25d87c9 | 505 | int git_branch_remote_name(git_buf *buf, git_repository *repo, const char *refname) |
2e3e8c88 JM |
506 | { |
507 | git_strarray remote_list = {0}; | |
97016f29 | 508 | size_t i; |
2e3e8c88 JM |
509 | git_remote *remote; |
510 | const git_refspec *fetchspec; | |
511 | int error = 0; | |
512 | char *remote_name = NULL; | |
513 | ||
c25aa7cd PP |
514 | GIT_ASSERT_ARG(buf); |
515 | GIT_ASSERT_ARG(repo); | |
516 | GIT_ASSERT_ARG(refname); | |
b25d87c9 | 517 | |
c25aa7cd PP |
518 | if ((error = git_buf_sanitize(buf)) < 0) |
519 | return error; | |
2e3e8c88 JM |
520 | |
521 | /* Verify that this is a remote branch */ | |
b25d87c9 | 522 | if (!git_reference__is_remote(refname)) { |
ac3d33df | 523 | git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a remote branch.", |
b25d87c9 | 524 | refname); |
2e3e8c88 JM |
525 | error = GIT_ERROR; |
526 | goto cleanup; | |
527 | } | |
528 | ||
529 | /* Get the remotes */ | |
530 | if ((error = git_remote_list(&remote_list, repo)) < 0) | |
531 | goto cleanup; | |
532 | ||
533 | /* Find matching remotes */ | |
534 | for (i = 0; i < remote_list.count; i++) { | |
209425ce | 535 | if ((error = git_remote_lookup(&remote, repo, remote_list.strings[i])) < 0) |
d59942c2 | 536 | continue; |
2e3e8c88 | 537 | |
b25d87c9 | 538 | fetchspec = git_remote__matching_dst_refspec(remote, refname); |
4330ab26 | 539 | if (fetchspec) { |
2e3e8c88 JM |
540 | /* If we have not already set out yet, then set |
541 | * it to the matching remote name. Otherwise | |
542 | * multiple remotes match this reference, and it | |
543 | * is ambiguous. */ | |
544 | if (!remote_name) { | |
545 | remote_name = remote_list.strings[i]; | |
546 | } else { | |
547 | git_remote_free(remote); | |
3e199f42 | 548 | |
ac3d33df | 549 | git_error_set(GIT_ERROR_REFERENCE, |
909d5494 | 550 | "reference '%s' is ambiguous", refname); |
2e3e8c88 JM |
551 | error = GIT_EAMBIGUOUS; |
552 | goto cleanup; | |
553 | } | |
554 | } | |
555 | ||
556 | git_remote_free(remote); | |
557 | } | |
558 | ||
559 | if (remote_name) { | |
97016f29 CMN |
560 | git_buf_clear(buf); |
561 | error = git_buf_puts(buf, remote_name); | |
2e3e8c88 | 562 | } else { |
ac3d33df | 563 | git_error_set(GIT_ERROR_REFERENCE, |
909d5494 | 564 | "could not determine remote for '%s'", refname); |
2e3e8c88 | 565 | error = GIT_ENOTFOUND; |
2e3e8c88 JM |
566 | } |
567 | ||
568 | cleanup: | |
b25d87c9 | 569 | if (error < 0) |
ac3d33df | 570 | git_buf_dispose(buf); |
b25d87c9 | 571 | |
22a2d3d5 | 572 | git_strarray_dispose(&remote_list); |
2e3e8c88 JM |
573 | return error; |
574 | } | |
575 | ||
a258d8e3 | 576 | int git_branch_upstream( |
3b4ba278 JG |
577 | git_reference **tracking_out, |
578 | const git_reference *branch) | |
bf031581 | 579 | { |
580 | int error; | |
581 | git_buf tracking_name = GIT_BUF_INIT; | |
582 | ||
b25d87c9 | 583 | if ((error = git_branch_upstream_name(&tracking_name, |
bf031581 | 584 | git_reference_owner(branch), git_reference_name(branch))) < 0) |
585 | return error; | |
586 | ||
587 | error = git_reference_lookup( | |
588 | tracking_out, | |
589 | git_reference_owner(branch), | |
590 | git_buf_cstr(&tracking_name)); | |
591 | ||
ac3d33df | 592 | git_buf_dispose(&tracking_name); |
bf031581 | 593 | return error; |
594 | } | |
595 | ||
d59942c2 CMN |
596 | static int unset_upstream(git_config *config, const char *shortname) |
597 | { | |
598 | git_buf buf = GIT_BUF_INIT; | |
599 | ||
600 | if (git_buf_printf(&buf, "branch.%s.remote", shortname) < 0) | |
601 | return -1; | |
602 | ||
603 | if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0) | |
604 | goto on_error; | |
605 | ||
606 | git_buf_clear(&buf); | |
607 | if (git_buf_printf(&buf, "branch.%s.merge", shortname) < 0) | |
608 | goto on_error; | |
609 | ||
610 | if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0) | |
611 | goto on_error; | |
612 | ||
ac3d33df | 613 | git_buf_dispose(&buf); |
d59942c2 CMN |
614 | return 0; |
615 | ||
616 | on_error: | |
ac3d33df | 617 | git_buf_dispose(&buf); |
d59942c2 CMN |
618 | return -1; |
619 | } | |
620 | ||
22a2d3d5 | 621 | int git_branch_set_upstream(git_reference *branch, const char *branch_name) |
d59942c2 | 622 | { |
22a2d3d5 | 623 | git_buf key = GIT_BUF_INIT, remote_name = GIT_BUF_INIT, merge_refspec = GIT_BUF_INIT; |
d59942c2 CMN |
624 | git_reference *upstream; |
625 | git_repository *repo; | |
626 | git_remote *remote = NULL; | |
627 | git_config *config; | |
22a2d3d5 | 628 | const char *refname, *shortname; |
5014fe95 | 629 | int local, error; |
d59942c2 CMN |
630 | const git_refspec *fetchspec; |
631 | ||
22a2d3d5 UG |
632 | refname = git_reference_name(branch); |
633 | if (!git_reference__is_branch(refname)) | |
634 | return not_a_local_branch(refname); | |
d59942c2 CMN |
635 | |
636 | if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0) | |
637 | return -1; | |
638 | ||
22a2d3d5 | 639 | shortname = refname + strlen(GIT_REFS_HEADS_DIR); |
d59942c2 | 640 | |
22a2d3d5 UG |
641 | /* We're unsetting, delegate and bail-out */ |
642 | if (branch_name == NULL) | |
d59942c2 CMN |
643 | return unset_upstream(config, shortname); |
644 | ||
645 | repo = git_reference_owner(branch); | |
646 | ||
22a2d3d5 UG |
647 | /* First we need to resolve name to a branch */ |
648 | if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_LOCAL) == 0) | |
d59942c2 | 649 | local = 1; |
22a2d3d5 | 650 | else if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_REMOTE) == 0) |
d59942c2 | 651 | local = 0; |
3e199f42 | 652 | else { |
ac3d33df | 653 | git_error_set(GIT_ERROR_REFERENCE, |
909d5494 | 654 | "cannot set upstream for branch '%s'", shortname); |
d59942c2 | 655 | return GIT_ENOTFOUND; |
3e199f42 | 656 | } |
d59942c2 CMN |
657 | |
658 | /* | |
22a2d3d5 UG |
659 | * If it's a local-tracking branch, its remote is "." (as "the local |
660 | * repository"), and the branch name is simply the refname. | |
661 | * Otherwise we need to figure out what the remote-tracking branch's | |
662 | * name on the remote is and use that. | |
d59942c2 CMN |
663 | */ |
664 | if (local) | |
22a2d3d5 | 665 | error = git_buf_puts(&remote_name, "."); |
d59942c2 | 666 | else |
22a2d3d5 | 667 | error = git_branch_remote_name(&remote_name, repo, git_reference_name(upstream)); |
5014fe95 CMN |
668 | |
669 | if (error < 0) | |
670 | goto on_error; | |
d59942c2 | 671 | |
22a2d3d5 | 672 | /* Update the upsteam branch config with the new name */ |
d59942c2 CMN |
673 | if (git_buf_printf(&key, "branch.%s.remote", shortname) < 0) |
674 | goto on_error; | |
675 | ||
22a2d3d5 | 676 | if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&remote_name)) < 0) |
d59942c2 CMN |
677 | goto on_error; |
678 | ||
679 | if (local) { | |
22a2d3d5 UG |
680 | /* A local branch uses the upstream refname directly */ |
681 | if (git_buf_puts(&merge_refspec, git_reference_name(upstream)) < 0) | |
d59942c2 CMN |
682 | goto on_error; |
683 | } else { | |
22a2d3d5 UG |
684 | /* We transform the upstream branch name according to the remote's refspecs */ |
685 | if (git_remote_lookup(&remote, repo, git_buf_cstr(&remote_name)) < 0) | |
d59942c2 CMN |
686 | goto on_error; |
687 | ||
4330ab26 | 688 | fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream)); |
22a2d3d5 | 689 | if (!fetchspec || git_refspec_rtransform(&merge_refspec, fetchspec, git_reference_name(upstream)) < 0) |
d59942c2 CMN |
690 | goto on_error; |
691 | ||
692 | git_remote_free(remote); | |
693 | remote = NULL; | |
694 | } | |
695 | ||
22a2d3d5 | 696 | /* Update the merge branch config with the refspec */ |
d59942c2 CMN |
697 | git_buf_clear(&key); |
698 | if (git_buf_printf(&key, "branch.%s.merge", shortname) < 0) | |
699 | goto on_error; | |
700 | ||
22a2d3d5 | 701 | if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&merge_refspec)) < 0) |
d59942c2 CMN |
702 | goto on_error; |
703 | ||
704 | git_reference_free(upstream); | |
ac3d33df | 705 | git_buf_dispose(&key); |
22a2d3d5 UG |
706 | git_buf_dispose(&remote_name); |
707 | git_buf_dispose(&merge_refspec); | |
d59942c2 CMN |
708 | |
709 | return 0; | |
710 | ||
711 | on_error: | |
712 | git_reference_free(upstream); | |
ac3d33df | 713 | git_buf_dispose(&key); |
22a2d3d5 UG |
714 | git_buf_dispose(&remote_name); |
715 | git_buf_dispose(&merge_refspec); | |
d59942c2 CMN |
716 | git_remote_free(remote); |
717 | ||
718 | return -1; | |
719 | } | |
720 | ||
0c78f685 | 721 | int git_branch_is_head( |
853b1407 | 722 | const git_reference *branch) |
0c78f685 | 723 | { |
724 | git_reference *head; | |
725 | bool is_same = false; | |
0532e7bb | 726 | int error; |
0c78f685 | 727 | |
c25aa7cd | 728 | GIT_ASSERT_ARG(branch); |
0c78f685 | 729 | |
730 | if (!git_reference_is_branch(branch)) | |
731 | return false; | |
732 | ||
0532e7bb | 733 | error = git_repository_head(&head, git_reference_owner(branch)); |
734 | ||
605da51a | 735 | if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND) |
0532e7bb | 736 | return false; |
737 | ||
738 | if (error < 0) | |
0c78f685 | 739 | return -1; |
740 | ||
741 | is_same = strcmp( | |
742 | git_reference_name(branch), | |
743 | git_reference_name(head)) == 0; | |
744 | ||
745 | git_reference_free(head); | |
746 | ||
747 | return is_same; | |
748 | } | |
c25aa7cd PP |
749 | |
750 | int git_branch_name_is_valid(int *valid, const char *name) | |
751 | { | |
752 | git_buf ref_name = GIT_BUF_INIT; | |
753 | int error = 0; | |
754 | ||
755 | GIT_ASSERT(valid); | |
756 | ||
757 | *valid = 0; | |
758 | ||
759 | /* | |
760 | * Discourage branch name starting with dash, | |
761 | * https://github.com/git/git/commit/6348624010888b | |
762 | * and discourage HEAD as branch name, | |
763 | * https://github.com/git/git/commit/a625b092cc5994 | |
764 | */ | |
765 | if (!name || name[0] == '-' || !git__strcmp(name, "HEAD")) | |
766 | goto done; | |
767 | ||
768 | if ((error = git_buf_puts(&ref_name, GIT_REFS_HEADS_DIR)) < 0 || | |
769 | (error = git_buf_puts(&ref_name, name)) < 0) | |
770 | goto done; | |
771 | ||
772 | error = git_reference_name_is_valid(valid, ref_name.ptr); | |
773 | ||
774 | done: | |
775 | git_buf_dispose(&ref_name); | |
776 | return error; | |
777 | } |