]>
Commit | Line | Data |
---|---|---|
731df570 | 1 | /* |
2 | * Copyright (C) 2009-2012 the libgit2 contributors | |
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" | |
731df570 | 13 | |
326ca710 | 14 | #include "git2/branch.h" |
15 | ||
731df570 | 16 | static int retrieve_branch_reference( |
17 | git_reference **branch_reference_out, | |
18 | git_repository *repo, | |
19 | const char *branch_name, | |
20 | int is_remote) | |
21 | { | |
22 | git_reference *branch; | |
23 | int error = -1; | |
24 | char *prefix; | |
25 | git_buf ref_name = GIT_BUF_INIT; | |
26 | ||
27 | *branch_reference_out = NULL; | |
28 | ||
29 | prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR; | |
30 | ||
31 | if (git_buf_joinpath(&ref_name, prefix, branch_name) < 0) | |
32 | goto cleanup; | |
33 | ||
34 | if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0) { | |
35 | giterr_set(GITERR_REFERENCE, | |
36 | "Cannot locate %s branch '%s'.", is_remote ? "remote-tracking" : "local", branch_name); | |
37 | goto cleanup; | |
38 | } | |
39 | ||
40 | *branch_reference_out = branch; | |
41 | ||
42 | cleanup: | |
43 | git_buf_free(&ref_name); | |
44 | return error; | |
45 | } | |
46 | ||
47 | static int create_error_invalid(const char *msg) | |
48 | { | |
49 | giterr_set(GITERR_INVALID, "Cannot create branch - %s", msg); | |
50 | return -1; | |
51 | } | |
52 | ||
1c947daa VM |
53 | static int not_a_local_branch(git_reference *ref) |
54 | { | |
55 | giterr_set(GITERR_INVALID, "Reference '%s' is not a local branch.", git_reference_name(ref)); | |
56 | return -1; | |
57 | } | |
58 | ||
731df570 | 59 | int git_branch_create( |
b308c11e | 60 | git_reference **ref_out, |
f0244463 | 61 | git_repository *repository, |
731df570 | 62 | const char *branch_name, |
63 | const git_object *target, | |
64 | int force) | |
65 | { | |
731df570 | 66 | git_object *commit = NULL; |
67 | git_reference *branch = NULL; | |
68 | git_buf canonical_branch_name = GIT_BUF_INIT; | |
69 | int error = -1; | |
70 | ||
b308c11e | 71 | assert(branch_name && target && ref_out); |
f0244463 | 72 | assert(git_object_owner(target) == repository); |
731df570 | 73 | |
d1445b75 | 74 | if (git_object_peel(&commit, (git_object *)target, GIT_OBJ_COMMIT) < 0) |
75 | return create_error_invalid("The given target does not resolve to a commit"); | |
731df570 | 76 | |
77 | if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) | |
78 | goto cleanup; | |
79 | ||
62993b61 | 80 | error = git_reference_create_oid(&branch, repository, |
81 | git_buf_cstr(&canonical_branch_name), git_object_id(commit), force); | |
731df570 | 82 | |
62993b61 | 83 | if (!error) |
84 | *ref_out = branch; | |
731df570 | 85 | |
86 | cleanup: | |
d1445b75 | 87 | git_object_free(commit); |
731df570 | 88 | git_buf_free(&canonical_branch_name); |
89 | return error; | |
90 | } | |
91 | ||
1c947daa | 92 | int git_branch_delete(git_reference *branch) |
731df570 | 93 | { |
4ba23be1 | 94 | int is_head; |
aba70781 | 95 | git_buf config_section = GIT_BUF_INIT; |
96 | int error = -1; | |
731df570 | 97 | |
1c947daa | 98 | assert(branch); |
731df570 | 99 | |
1c947daa VM |
100 | if (!git_reference_is_branch(branch) && |
101 | !git_reference_is_remote(branch)) { | |
102 | giterr_set(GITERR_INVALID, "Reference '%s' is not a valid branch.", git_reference_name(branch)); | |
103 | return -1; | |
104 | } | |
731df570 | 105 | |
4ba23be1 | 106 | if ((is_head = git_branch_is_head(branch)) < 0) |
107 | return is_head; | |
731df570 | 108 | |
4ba23be1 | 109 | if (is_head) { |
110 | giterr_set(GITERR_REFERENCE, | |
111 | "Cannot delete branch '%s' as it is the current HEAD of the repository.", git_reference_name(branch)); | |
112 | return -1; | |
731df570 | 113 | } |
114 | ||
aba70781 | 115 | if (git_buf_printf(&config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) |
116 | goto on_error; | |
117 | ||
118 | if (git_config_rename_section( | |
0b98a8a4 | 119 | git_reference_owner(branch), |
aba70781 | 120 | git_buf_cstr(&config_section), |
383f164a | 121 | NULL) < 0) |
0b98a8a4 | 122 | goto on_error; |
123 | ||
aba70781 | 124 | if (git_reference_delete(branch) < 0) |
125 | goto on_error; | |
126 | ||
127 | error = 0; | |
128 | ||
129 | on_error: | |
130 | git_buf_free(&config_section); | |
131 | return error; | |
731df570 | 132 | } |
133 | ||
a8fd805e | 134 | typedef struct { |
135 | int (*branch_cb)( | |
136 | const char *branch_name, | |
137 | git_branch_t branch_type, | |
138 | void *payload); | |
139 | void *callback_payload; | |
140 | unsigned int branch_type; | |
141 | } branch_foreach_filter; | |
142 | ||
143 | static int branch_foreach_cb(const char *branch_name, void *payload) | |
144 | { | |
145 | branch_foreach_filter *filter = (branch_foreach_filter *)payload; | |
146 | ||
147 | if (filter->branch_type & GIT_BRANCH_LOCAL && | |
148 | git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0) | |
149 | return filter->branch_cb(branch_name + strlen(GIT_REFS_HEADS_DIR), GIT_BRANCH_LOCAL, filter->callback_payload); | |
150 | ||
151 | if (filter->branch_type & GIT_BRANCH_REMOTE && | |
152 | git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0) | |
153 | return filter->branch_cb(branch_name + strlen(GIT_REFS_REMOTES_DIR), GIT_BRANCH_REMOTE, filter->callback_payload); | |
154 | ||
155 | return 0; | |
156 | } | |
157 | ||
158 | int git_branch_foreach( | |
159 | git_repository *repo, | |
160 | unsigned int list_flags, | |
161 | int (*branch_cb)( | |
162 | const char *branch_name, | |
163 | git_branch_t branch_type, | |
164 | void *payload), | |
165 | void *payload | |
166 | ) | |
167 | { | |
168 | branch_foreach_filter filter; | |
169 | ||
170 | filter.branch_cb = branch_cb; | |
171 | filter.branch_type = list_flags; | |
172 | filter.callback_payload = payload; | |
173 | ||
174 | return git_reference_foreach(repo, GIT_REF_LISTALL, &branch_foreach_cb, (void *)&filter); | |
175 | } | |
176 | ||
bf9e8cc8 | 177 | int git_branch_move( |
178 | git_reference *branch, | |
179 | const char *new_branch_name, | |
180 | int force) | |
181 | { | |
383f164a | 182 | git_buf new_reference_name = GIT_BUF_INIT, |
aba70781 | 183 | old_config_section = GIT_BUF_INIT, |
184 | new_config_section = GIT_BUF_INIT; | |
bf9e8cc8 | 185 | int error; |
6a8bcfa4 | 186 | |
bf9e8cc8 | 187 | assert(branch && new_branch_name); |
188 | ||
189 | if (!git_reference_is_branch(branch)) | |
190 | return not_a_local_branch(branch); | |
4615f0f7 | 191 | |
6a625435 CMN |
192 | if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) |
193 | goto cleanup; | |
4615f0f7 | 194 | |
aba70781 | 195 | if (git_buf_printf( |
196 | &old_config_section, | |
197 | "branch.%s", | |
198 | git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) | |
199 | goto cleanup; | |
383f164a | 200 | |
201 | if ((error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force)) < 0) | |
202 | goto cleanup; | |
203 | ||
aba70781 | 204 | if (git_buf_printf(&new_config_section, "branch.%s", new_branch_name) < 0) |
205 | goto cleanup; | |
206 | ||
207 | if ((error = git_config_rename_section( | |
383f164a | 208 | git_reference_owner(branch), |
aba70781 | 209 | git_buf_cstr(&old_config_section), |
210 | git_buf_cstr(&new_config_section))) < 0) | |
383f164a | 211 | goto cleanup; |
4615f0f7 | 212 | |
6a625435 | 213 | cleanup: |
6a8bcfa4 | 214 | git_buf_free(&new_reference_name); |
aba70781 | 215 | git_buf_free(&old_config_section); |
216 | git_buf_free(&new_config_section); | |
6a8bcfa4 | 217 | |
6a625435 | 218 | return error; |
4615f0f7 | 219 | } |
eed378b6 | 220 | |
221 | int git_branch_lookup( | |
222 | git_reference **ref_out, | |
223 | git_repository *repo, | |
224 | const char *branch_name, | |
225 | git_branch_t branch_type) | |
226 | { | |
227 | assert(ref_out && repo && branch_name); | |
228 | ||
229 | return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE); | |
230 | } | |
fb910281 | 231 | |
f0244463 VM |
232 | static int retrieve_tracking_configuration( |
233 | const char **out, git_reference *branch, const char *format) | |
fb910281 | 234 | { |
235 | git_config *config; | |
236 | git_buf buf = GIT_BUF_INIT; | |
237 | int error; | |
238 | ||
239 | if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0) | |
240 | return -1; | |
241 | ||
242 | if (git_buf_printf(&buf, format, | |
243 | git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) | |
244 | return -1; | |
245 | ||
246 | error = git_config_get_string(out, config, git_buf_cstr(&buf)); | |
247 | git_buf_free(&buf); | |
248 | return error; | |
249 | } | |
250 | ||
251 | int git_branch_tracking( | |
252 | git_reference **tracking_out, | |
253 | git_reference *branch) | |
254 | { | |
255 | const char *remote_name, *merge_name; | |
256 | git_buf buf = GIT_BUF_INIT; | |
257 | int error = -1; | |
258 | git_remote *remote = NULL; | |
259 | const git_refspec *refspec; | |
260 | ||
261 | assert(tracking_out && branch); | |
262 | ||
263 | if (!git_reference_is_branch(branch)) | |
264 | return not_a_local_branch(branch); | |
265 | ||
266 | if ((error = retrieve_tracking_configuration(&remote_name, branch, "branch.%s.remote")) < 0) | |
267 | goto cleanup; | |
268 | ||
269 | if ((error = retrieve_tracking_configuration(&merge_name, branch, "branch.%s.merge")) < 0) | |
270 | goto cleanup; | |
b0f6e45d ET |
271 | |
272 | if (remote_name == NULL || merge_name == NULL) { | |
273 | error = GIT_ENOTFOUND; | |
274 | goto cleanup; | |
275 | } | |
fb910281 | 276 | |
277 | if (strcmp(".", remote_name) != 0) { | |
278 | if ((error = git_remote_load(&remote, git_reference_owner(branch), remote_name)) < 0) | |
279 | goto cleanup; | |
280 | ||
281 | refspec = git_remote_fetchspec(remote); | |
e16fc07f | 282 | if (refspec == NULL |
283 | || refspec->src == NULL | |
284 | || refspec->dst == NULL) { | |
285 | error = GIT_ENOTFOUND; | |
286 | goto cleanup; | |
fb910281 | 287 | } |
288 | ||
289 | if (git_refspec_transform_r(&buf, refspec, merge_name) < 0) | |
290 | goto cleanup; | |
291 | } else | |
292 | if (git_buf_sets(&buf, merge_name) < 0) | |
293 | goto cleanup; | |
294 | ||
295 | error = git_reference_lookup( | |
296 | tracking_out, | |
297 | git_reference_owner(branch), | |
298 | git_buf_cstr(&buf)); | |
299 | ||
300 | cleanup: | |
301 | git_remote_free(remote); | |
302 | git_buf_free(&buf); | |
303 | return error; | |
304 | } | |
0c78f685 | 305 | |
306 | int git_branch_is_head( | |
307 | git_reference *branch) | |
308 | { | |
309 | git_reference *head; | |
310 | bool is_same = false; | |
0532e7bb | 311 | int error; |
0c78f685 | 312 | |
313 | assert(branch); | |
314 | ||
315 | if (!git_reference_is_branch(branch)) | |
316 | return false; | |
317 | ||
0532e7bb | 318 | error = git_repository_head(&head, git_reference_owner(branch)); |
319 | ||
8b05bea8 | 320 | if (error == GIT_EORPHANEDHEAD) |
0532e7bb | 321 | return false; |
322 | ||
323 | if (error < 0) | |
0c78f685 | 324 | return -1; |
325 | ||
326 | is_same = strcmp( | |
327 | git_reference_name(branch), | |
328 | git_reference_name(head)) == 0; | |
329 | ||
330 | git_reference_free(head); | |
331 | ||
332 | return is_same; | |
333 | } |