]>
Commit | Line | Data |
---|---|---|
9c82357b | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
9c82357b | 3 | * |
bb742ede VM |
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. | |
9c82357b CMN |
6 | */ |
7 | ||
9c82357b CMN |
8 | #include "git2/config.h" |
9 | #include "git2/types.h" | |
c07d9c95 | 10 | #include "git2/oid.h" |
1ffd0806 | 11 | #include "git2/net.h" |
9c82357b CMN |
12 | |
13 | #include "config.h" | |
14 | #include "repository.h" | |
15 | #include "remote.h" | |
e1d88030 | 16 | #include "fetch.h" |
441f57c2 | 17 | #include "refs.h" |
b0f6e45d ET |
18 | #include "refspec.h" |
19 | #include "fetchhead.h" | |
9c82357b | 20 | |
8171998f CMN |
21 | #include <regex.h> |
22 | ||
4330ab26 | 23 | static int add_refspec(git_remote *remote, const char *string, bool is_fetch) |
9c82357b | 24 | { |
4330ab26 | 25 | git_refspec *spec; |
9c82357b | 26 | |
4330ab26 CMN |
27 | spec = git__calloc(1, sizeof(git_refspec)); |
28 | GITERR_CHECK_ALLOC(spec); | |
29 | ||
1be680c4 CMN |
30 | if (git_refspec__parse(spec, string, is_fetch) < 0) { |
31 | git__free(spec); | |
32 | return -1; | |
33 | } | |
4330ab26 CMN |
34 | |
35 | spec->push = !is_fetch; | |
1be680c4 CMN |
36 | if (git_vector_insert(&remote->refspecs, spec) < 0) { |
37 | git_refspec__free(spec); | |
38 | git__free(spec); | |
39 | return -1; | |
40 | } | |
9c82357b | 41 | |
4330ab26 | 42 | return 0; |
9c82357b CMN |
43 | } |
44 | ||
24f2f94e CMN |
45 | static int download_tags_value(git_remote *remote, git_config *cfg) |
46 | { | |
47 | const char *val; | |
48 | git_buf buf = GIT_BUF_INIT; | |
49 | int error; | |
50 | ||
df50512a | 51 | /* The 0 value is the default (auto), let's see if we need to change it */ |
24f2f94e CMN |
52 | if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0) |
53 | return -1; | |
54 | ||
55 | error = git_config_get_string(&val, cfg, git_buf_cstr(&buf)); | |
56 | git_buf_free(&buf); | |
57 | if (!error && !strcmp(val, "--no-tags")) | |
58 | remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; | |
3230a44f CMN |
59 | else if (!error && !strcmp(val, "--tags")) |
60 | remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; | |
24f2f94e | 61 | |
bf6bebe2 RB |
62 | if (error == GIT_ENOTFOUND) { |
63 | giterr_clear(); | |
24f2f94e | 64 | error = 0; |
bf6bebe2 | 65 | } |
24f2f94e CMN |
66 | |
67 | return error; | |
68 | } | |
69 | ||
032ba9e4 | 70 | static int ensure_remote_name_is_valid(const char *name) |
71 | { | |
2bca5b67 | 72 | int error = 0; |
032ba9e4 | 73 | |
2bca5b67 | 74 | if (!git_remote_is_valid_name(name)) { |
032ba9e4 | 75 | giterr_set( |
76 | GITERR_CONFIG, | |
77 | "'%s' is not a valid remote name.", name); | |
78 | error = GIT_EINVALIDSPEC; | |
79 | } | |
80 | ||
81 | return error; | |
82 | } | |
83 | ||
af6dab7e PK |
84 | static int get_check_cert(git_repository *repo) |
85 | { | |
86 | git_config *cfg; | |
87 | const char *val; | |
88 | int check_cert; | |
89 | ||
90 | assert(repo); | |
91 | ||
92 | /* Go through the possible sources for SSL verification settings, from | |
93 | * most specific to least specific. */ | |
94 | ||
95 | /* GIT_SSL_NO_VERIFY environment variable */ | |
96 | if ((val = getenv("GIT_SSL_NO_VERIFY")) && | |
97 | !git_config_parse_bool(&check_cert, val)) | |
98 | return !check_cert; | |
99 | ||
100 | /* http.sslVerify config setting */ | |
101 | if (!git_repository_config__weakptr(&cfg, repo) && | |
102 | !git_config_get_bool(&check_cert, cfg, "http.sslVerify")) | |
103 | return check_cert; | |
104 | ||
105 | /* By default, we *DO* want to verify the certificate. */ | |
106 | return 1; | |
107 | } | |
108 | ||
874dcb25 | 109 | static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) |
778e1c73 CMN |
110 | { |
111 | git_remote *remote; | |
44f36f6e BS |
112 | git_buf fetchbuf = GIT_BUF_INIT; |
113 | int error = -1; | |
778e1c73 | 114 | |
4bef3565 | 115 | /* name is optional */ |
874dcb25 | 116 | assert(out && repo && url); |
617bfdf4 | 117 | |
59bccf33 | 118 | remote = git__calloc(1, sizeof(git_remote)); |
4376f7f6 | 119 | GITERR_CHECK_ALLOC(remote); |
778e1c73 | 120 | |
778e1c73 | 121 | remote->repo = repo; |
af6dab7e | 122 | remote->check_cert = (unsigned)get_check_cert(repo); |
b0f6e45d | 123 | remote->update_fetchhead = 1; |
617bfdf4 | 124 | |
4376f7f6 | 125 | if (git_vector_init(&remote->refs, 32, NULL) < 0) |
44f36f6e | 126 | goto on_error; |
d88d4311 | 127 | |
778e1c73 | 128 | remote->url = git__strdup(url); |
4376f7f6 | 129 | GITERR_CHECK_ALLOC(remote->url); |
778e1c73 | 130 | |
617bfdf4 CMN |
131 | if (name != NULL) { |
132 | remote->name = git__strdup(name); | |
4376f7f6 | 133 | GITERR_CHECK_ALLOC(remote->name); |
617bfdf4 CMN |
134 | } |
135 | ||
baaa8a44 | 136 | if (fetch != NULL) { |
4330ab26 | 137 | if (add_refspec(remote, fetch, true) < 0) |
baaa8a44 CMN |
138 | goto on_error; |
139 | } | |
140 | ||
215af2cc | 141 | if (!name) |
142 | /* A remote without a name doesn't download tags */ | |
c648d4a8 | 143 | remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; |
c648d4a8 | 144 | |
778e1c73 | 145 | *out = remote; |
44f36f6e | 146 | git_buf_free(&fetchbuf); |
4376f7f6 | 147 | return 0; |
baaa8a44 CMN |
148 | |
149 | on_error: | |
150 | git_remote_free(remote); | |
44f36f6e BS |
151 | git_buf_free(&fetchbuf); |
152 | return error; | |
778e1c73 CMN |
153 | } |
154 | ||
592f466c BS |
155 | static int ensure_remote_doesnot_exist(git_repository *repo, const char *name) |
156 | { | |
157 | int error; | |
158 | git_remote *remote; | |
159 | ||
160 | error = git_remote_load(&remote, repo, name); | |
161 | ||
162 | if (error == GIT_ENOTFOUND) | |
163 | return 0; | |
164 | ||
165 | if (error < 0) | |
166 | return error; | |
167 | ||
168 | git_remote_free(remote); | |
169 | ||
170 | giterr_set( | |
171 | GITERR_CONFIG, | |
172 | "Remote '%s' already exists.", name); | |
173 | ||
174 | return GIT_EEXISTS; | |
175 | } | |
176 | ||
f19304d2 | 177 | |
874dcb25 BS |
178 | int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url) |
179 | { | |
180 | git_buf buf = GIT_BUF_INIT; | |
c5193e3c | 181 | git_remote *remote = NULL; |
874dcb25 BS |
182 | int error; |
183 | ||
184 | if ((error = ensure_remote_name_is_valid(name)) < 0) | |
185 | return error; | |
186 | ||
f19304d2 | 187 | if ((error = ensure_remote_doesnot_exist(repo, name)) < 0) |
188 | return error; | |
189 | ||
874dcb25 BS |
190 | if (git_buf_printf(&buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0) |
191 | return -1; | |
192 | ||
c5193e3c | 193 | if (create_internal(&remote, repo, name, url, git_buf_cstr(&buf)) < 0) |
874dcb25 BS |
194 | goto on_error; |
195 | ||
196 | git_buf_free(&buf); | |
197 | ||
c5193e3c | 198 | if (git_remote_save(remote) < 0) |
874dcb25 BS |
199 | goto on_error; |
200 | ||
c5193e3c | 201 | *out = remote; |
202 | ||
874dcb25 BS |
203 | return 0; |
204 | ||
205 | on_error: | |
206 | git_buf_free(&buf); | |
c5193e3c | 207 | git_remote_free(remote); |
874dcb25 BS |
208 | return -1; |
209 | } | |
210 | ||
0642c143 | 211 | int git_remote_create_inmemory(git_remote **out, git_repository *repo, const char *fetch, const char *url) |
874dcb25 BS |
212 | { |
213 | int error; | |
214 | git_remote *remote; | |
215 | ||
79000951 | 216 | if ((error = create_internal(&remote, repo, NULL, url, fetch)) < 0) |
874dcb25 BS |
217 | return error; |
218 | ||
874dcb25 BS |
219 | *out = remote; |
220 | return 0; | |
221 | } | |
222 | ||
4330ab26 CMN |
223 | struct refspec_cb_data { |
224 | git_remote *remote; | |
225 | int fetch; | |
226 | }; | |
227 | ||
228 | static int refspec_cb(const git_config_entry *entry, void *payload) | |
229 | { | |
230 | const struct refspec_cb_data *data = (struct refspec_cb_data *)payload; | |
231 | ||
232 | return add_refspec(data->remote, entry->value, data->fetch); | |
233 | } | |
234 | ||
bf6bebe2 | 235 | static int get_optional_config( |
c9ffa84b | 236 | bool *found, git_config *config, git_buf *buf, |
237 | git_config_foreach_cb cb, void *payload) | |
bf6bebe2 RB |
238 | { |
239 | int error = 0; | |
240 | const char *key = git_buf_cstr(buf); | |
241 | ||
242 | if (git_buf_oom(buf)) | |
243 | return -1; | |
244 | ||
245 | if (cb != NULL) | |
4efa3290 | 246 | error = git_config_get_multivar_foreach(config, key, NULL, cb, payload); |
bf6bebe2 RB |
247 | else |
248 | error = git_config_get_string(payload, config, key); | |
249 | ||
c9ffa84b | 250 | if (found) |
251 | *found = !error; | |
252 | ||
bf6bebe2 RB |
253 | if (error == GIT_ENOTFOUND) { |
254 | giterr_clear(); | |
255 | error = 0; | |
256 | } | |
257 | ||
258 | if (error < 0) | |
259 | error = -1; | |
260 | ||
261 | return error; | |
262 | } | |
263 | ||
9462c471 | 264 | int git_remote_load(git_remote **out, git_repository *repo, const char *name) |
9c82357b CMN |
265 | { |
266 | git_remote *remote; | |
f0f3a18a | 267 | git_buf buf = GIT_BUF_INIT; |
9c82357b | 268 | const char *val; |
4376f7f6 | 269 | int error = 0; |
9462c471 | 270 | git_config *config; |
4330ab26 | 271 | struct refspec_cb_data data; |
c9ffa84b | 272 | bool optional_setting_found = false, found; |
4330ab26 | 273 | |
9462c471 VM |
274 | assert(out && repo && name); |
275 | ||
032ba9e4 | 276 | if ((error = ensure_remote_name_is_valid(name)) < 0) |
277 | return error; | |
278 | ||
4376f7f6 CMN |
279 | if (git_repository_config__weakptr(&config, repo) < 0) |
280 | return -1; | |
4bef3565 | 281 | |
9c82357b | 282 | remote = git__malloc(sizeof(git_remote)); |
4376f7f6 | 283 | GITERR_CHECK_ALLOC(remote); |
9c82357b CMN |
284 | |
285 | memset(remote, 0x0, sizeof(git_remote)); | |
af6dab7e | 286 | remote->check_cert = (unsigned)get_check_cert(repo); |
b0f6e45d | 287 | remote->update_fetchhead = 1; |
9c82357b | 288 | remote->name = git__strdup(name); |
4376f7f6 | 289 | GITERR_CHECK_ALLOC(remote->name); |
9c82357b | 290 | |
4330ab26 | 291 | if ((git_vector_init(&remote->refs, 32, NULL) < 0) || |
1be680c4 | 292 | (git_vector_init(&remote->refspecs, 2, NULL))) { |
2fb9d6de | 293 | error = -1; |
294 | goto cleanup; | |
295 | } | |
d88d4311 | 296 | |
2fb9d6de | 297 | if (git_buf_printf(&buf, "remote.%s.url", name) < 0) { |
298 | error = -1; | |
299 | goto cleanup; | |
300 | } | |
9c82357b | 301 | |
c9ffa84b | 302 | if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) |
9c82357b | 303 | goto cleanup; |
bf6bebe2 | 304 | |
c9ffa84b | 305 | optional_setting_found |= found; |
306 | ||
9462c471 | 307 | remote->repo = repo; |
c9ffa84b | 308 | |
309 | if (found && strlen(val) > 0) { | |
310 | remote->url = git__strdup(val); | |
311 | GITERR_CHECK_ALLOC(remote->url); | |
312 | } | |
9c82357b | 313 | |
bf6bebe2 | 314 | val = NULL; |
3ed4b501 | 315 | git_buf_clear(&buf); |
bf6bebe2 | 316 | git_buf_printf(&buf, "remote.%s.pushurl", name); |
3ed4b501 | 317 | |
c9ffa84b | 318 | if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0) |
3ed4b501 | 319 | goto cleanup; |
3ed4b501 | 320 | |
c9ffa84b | 321 | optional_setting_found |= found; |
322 | ||
323 | if (!optional_setting_found) { | |
324 | error = GIT_ENOTFOUND; | |
3ed4b501 | 325 | goto cleanup; |
c9ffa84b | 326 | } |
3ed4b501 | 327 | |
c9ffa84b | 328 | if (found && strlen(val) > 0) { |
3ed4b501 SC |
329 | remote->pushurl = git__strdup(val); |
330 | GITERR_CHECK_ALLOC(remote->pushurl); | |
331 | } | |
332 | ||
4330ab26 CMN |
333 | data.remote = remote; |
334 | data.fetch = true; | |
f0f3a18a | 335 | git_buf_clear(&buf); |
bf6bebe2 RB |
336 | git_buf_printf(&buf, "remote.%s.fetch", name); |
337 | ||
c9ffa84b | 338 | if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) |
2fb9d6de | 339 | goto cleanup; |
9c82357b | 340 | |
4330ab26 | 341 | data.fetch = false; |
bf6bebe2 RB |
342 | git_buf_clear(&buf); |
343 | git_buf_printf(&buf, "remote.%s.push", name); | |
9c82357b | 344 | |
c9ffa84b | 345 | if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0) |
9c82357b CMN |
346 | goto cleanup; |
347 | ||
24f2f94e CMN |
348 | if (download_tags_value(remote, config) < 0) |
349 | goto cleanup; | |
350 | ||
9c82357b CMN |
351 | *out = remote; |
352 | ||
353 | cleanup: | |
f0f3a18a | 354 | git_buf_free(&buf); |
9462c471 | 355 | |
4376f7f6 | 356 | if (error < 0) |
9c82357b CMN |
357 | git_remote_free(remote); |
358 | ||
359 | return error; | |
360 | } | |
361 | ||
4330ab26 | 362 | static int update_config_refspec(const git_remote *remote, git_config *config, int direction) |
4fe5520a | 363 | { |
4330ab26 | 364 | git_buf name = GIT_BUF_INIT; |
66566516 | 365 | unsigned int push; |
4330ab26 CMN |
366 | const char *dir; |
367 | size_t i; | |
bf6bebe2 | 368 | int error = 0; |
4fe5520a | 369 | |
4330ab26 CMN |
370 | push = direction == GIT_DIRECTION_PUSH; |
371 | dir = push ? "push" : "fetch"; | |
4fe5520a | 372 | |
4330ab26 CMN |
373 | if (git_buf_printf(&name, "remote.%s.%s", remote->name, dir) < 0) |
374 | return -1; | |
4fe5520a | 375 | |
4330ab26 | 376 | /* Clear out the existing config */ |
bf6bebe2 | 377 | while (!error) |
4330ab26 | 378 | error = git_config_delete_entry(config, git_buf_cstr(&name)); |
4fe5520a | 379 | |
4330ab26 CMN |
380 | if (error != GIT_ENOTFOUND) |
381 | return error; | |
382 | ||
1be680c4 | 383 | for (i = 0; i < remote->refspecs.length; i++) { |
4330ab26 | 384 | git_refspec *spec = git_vector_get(&remote->refspecs, i); |
4330ab26 CMN |
385 | |
386 | if (spec->push != push) | |
387 | continue; | |
388 | ||
bf6bebe2 RB |
389 | if ((error = git_config_set_multivar( |
390 | config, git_buf_cstr(&name), "", spec->string)) < 0) { | |
4330ab26 CMN |
391 | goto cleanup; |
392 | } | |
393 | } | |
394 | ||
395 | giterr_clear(); | |
396 | error = 0; | |
4fe5520a | 397 | |
398 | cleanup: | |
399 | git_buf_free(&name); | |
4fe5520a | 400 | |
401 | return error; | |
402 | } | |
403 | ||
89e5ed98 CMN |
404 | int git_remote_save(const git_remote *remote) |
405 | { | |
218c88a9 | 406 | int error; |
89e5ed98 | 407 | git_config *config; |
218c88a9 | 408 | const char *tagopt = NULL; |
4fe5520a | 409 | git_buf buf = GIT_BUF_INIT; |
89e5ed98 | 410 | |
e497b16c | 411 | assert(remote); |
412 | ||
c07b52df | 413 | if (!remote->name) { |
874dcb25 BS |
414 | giterr_set(GITERR_INVALID, "Can't save an in-memory remote."); |
415 | return GIT_EINVALIDSPEC; | |
a71c27cc BS |
416 | } |
417 | ||
032ba9e4 | 418 | if ((error = ensure_remote_name_is_valid(remote->name)) < 0) |
419 | return error; | |
89e5ed98 | 420 | |
4376f7f6 CMN |
421 | if (git_repository_config__weakptr(&config, remote->repo) < 0) |
422 | return -1; | |
89e5ed98 | 423 | |
cb020f0d | 424 | if (git_buf_printf(&buf, "remote.%s.url", remote->name) < 0) |
4376f7f6 | 425 | return -1; |
89e5ed98 | 426 | |
4376f7f6 CMN |
427 | if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) { |
428 | git_buf_free(&buf); | |
429 | return -1; | |
430 | } | |
89e5ed98 | 431 | |
413d5563 SC |
432 | git_buf_clear(&buf); |
433 | if (git_buf_printf(&buf, "remote.%s.pushurl", remote->name) < 0) | |
434 | return -1; | |
3ed4b501 | 435 | |
413d5563 | 436 | if (remote->pushurl) { |
3ed4b501 SC |
437 | if (git_config_set_string(config, git_buf_cstr(&buf), remote->pushurl) < 0) { |
438 | git_buf_free(&buf); | |
439 | return -1; | |
440 | } | |
413d5563 | 441 | } else { |
54b2a37a | 442 | int error = git_config_delete_entry(config, git_buf_cstr(&buf)); |
413d5563 SC |
443 | if (error == GIT_ENOTFOUND) { |
444 | error = 0; | |
c48e8700 | 445 | giterr_clear(); |
413d5563 SC |
446 | } |
447 | if (error < 0) { | |
448 | git_buf_free(&buf); | |
449 | return -1; | |
450 | } | |
3ed4b501 SC |
451 | } |
452 | ||
4330ab26 CMN |
453 | if (update_config_refspec(remote, config, GIT_DIRECTION_FETCH) < 0) |
454 | goto on_error; | |
89e5ed98 | 455 | |
4330ab26 CMN |
456 | if (update_config_refspec(remote, config, GIT_DIRECTION_PUSH) < 0) |
457 | goto on_error; | |
89e5ed98 | 458 | |
218c88a9 CMN |
459 | /* |
460 | * What action to take depends on the old and new values. This | |
461 | * is describes by the table below. tagopt means whether the | |
462 | * is already a value set in the config | |
463 | * | |
464 | * AUTO ALL or NONE | |
465 | * +-----------------------+ | |
466 | * tagopt | remove | set | | |
467 | * +---------+-------------| | |
468 | * !tagopt | nothing | set | | |
469 | * +---------+-------------+ | |
470 | */ | |
471 | ||
472 | git_buf_clear(&buf); | |
473 | if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0) | |
474 | goto on_error; | |
475 | ||
476 | error = git_config_get_string(&tagopt, config, git_buf_cstr(&buf)); | |
477 | if (error < 0 && error != GIT_ENOTFOUND) | |
478 | goto on_error; | |
479 | ||
480 | if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { | |
481 | if (git_config_set_string(config, git_buf_cstr(&buf), "--tags") < 0) | |
482 | goto on_error; | |
483 | } else if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_NONE) { | |
484 | if (git_config_set_string(config, git_buf_cstr(&buf), "--no-tags") < 0) | |
485 | goto on_error; | |
486 | } else if (tagopt) { | |
54b2a37a | 487 | if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0) |
218c88a9 CMN |
488 | goto on_error; |
489 | } | |
490 | ||
89e5ed98 | 491 | git_buf_free(&buf); |
4376f7f6 CMN |
492 | |
493 | return 0; | |
494 | ||
495 | on_error: | |
496 | git_buf_free(&buf); | |
4376f7f6 | 497 | return -1; |
89e5ed98 CMN |
498 | } |
499 | ||
df705148 | 500 | const char *git_remote_name(const git_remote *remote) |
9c82357b | 501 | { |
4bef3565 | 502 | assert(remote); |
9c82357b CMN |
503 | return remote->name; |
504 | } | |
505 | ||
85e1eded ES |
506 | git_repository *git_remote_owner(const git_remote *remote) |
507 | { | |
508 | assert(remote); | |
509 | return remote->repo; | |
510 | } | |
511 | ||
df705148 | 512 | const char *git_remote_url(const git_remote *remote) |
9c82357b | 513 | { |
4bef3565 | 514 | assert(remote); |
9c82357b CMN |
515 | return remote->url; |
516 | } | |
517 | ||
76501590 SC |
518 | int git_remote_set_url(git_remote *remote, const char* url) |
519 | { | |
520 | assert(remote); | |
521 | assert(url); | |
522 | ||
523 | git__free(remote->url); | |
524 | remote->url = git__strdup(url); | |
525 | GITERR_CHECK_ALLOC(remote->url); | |
526 | ||
527 | return 0; | |
528 | } | |
529 | ||
df705148 | 530 | const char *git_remote_pushurl(const git_remote *remote) |
76501590 SC |
531 | { |
532 | assert(remote); | |
533 | return remote->pushurl; | |
534 | } | |
535 | ||
536 | int git_remote_set_pushurl(git_remote *remote, const char* url) | |
537 | { | |
538 | assert(remote); | |
539 | ||
540 | git__free(remote->pushurl); | |
541 | if (url) { | |
542 | remote->pushurl = git__strdup(url); | |
543 | GITERR_CHECK_ALLOC(remote->pushurl); | |
544 | } else { | |
545 | remote->pushurl = NULL; | |
546 | } | |
547 | return 0; | |
548 | } | |
549 | ||
eff5b499 SC |
550 | const char* git_remote__urlfordirection(git_remote *remote, int direction) |
551 | { | |
552 | assert(remote); | |
553 | ||
b83c92dd | 554 | assert(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); |
555 | ||
df705148 | 556 | if (direction == GIT_DIRECTION_FETCH) { |
eff5b499 SC |
557 | return remote->url; |
558 | } | |
559 | ||
df705148 | 560 | if (direction == GIT_DIRECTION_PUSH) { |
eff5b499 SC |
561 | return remote->pushurl ? remote->pushurl : remote->url; |
562 | } | |
563 | ||
564 | return NULL; | |
565 | } | |
566 | ||
df705148 | 567 | int git_remote_connect(git_remote *remote, git_direction direction) |
9ba49bb5 | 568 | { |
9ba49bb5 | 569 | git_transport *t; |
c0c39025 | 570 | const char *url; |
41fb1ca0 | 571 | int flags = GIT_TRANSPORTFLAGS_NONE; |
9ba49bb5 | 572 | |
4bef3565 VM |
573 | assert(remote); |
574 | ||
41fb1ca0 PK |
575 | t = remote->transport; |
576 | ||
c0c39025 | 577 | url = git_remote__urlfordirection(remote, direction); |
44bc0c6a | 578 | if (url == NULL ) { |
579 | giterr_set(GITERR_INVALID, | |
580 | "Malformed remote '%s' - missing URL", remote->name); | |
eff5b499 | 581 | return -1; |
44bc0c6a | 582 | } |
eff5b499 | 583 | |
41fb1ca0 PK |
584 | /* A transport could have been supplied in advance with |
585 | * git_remote_set_transport */ | |
613d5eb9 | 586 | if (!t && git_transport_new(&t, remote, url) < 0) |
4376f7f6 | 587 | return -1; |
9ba49bb5 | 588 | |
41fb1ca0 | 589 | if (t->set_callbacks && |
df705148 | 590 | t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.payload) < 0) |
41fb1ca0 | 591 | goto on_error; |
613d5eb9 | 592 | |
41fb1ca0 PK |
593 | if (!remote->check_cert) |
594 | flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT; | |
e03e71da | 595 | |
59bccf33 | 596 | if (t->connect(t, url, remote->cred_acquire_cb, remote->cred_acquire_payload, direction, flags) < 0) |
4376f7f6 | 597 | goto on_error; |
9ba49bb5 CMN |
598 | |
599 | remote->transport = t; | |
600 | ||
4376f7f6 | 601 | return 0; |
9ba49bb5 | 602 | |
4376f7f6 CMN |
603 | on_error: |
604 | t->free(t); | |
613d5eb9 PK |
605 | |
606 | if (t == remote->transport) | |
607 | remote->transport = NULL; | |
608 | ||
4376f7f6 | 609 | return -1; |
9ba49bb5 CMN |
610 | } |
611 | ||
d88d4311 | 612 | int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) |
9ba49bb5 | 613 | { |
d88d4311 VM |
614 | assert(remote); |
615 | ||
41fb1ca0 | 616 | return remote->transport->ls(remote->transport, list_cb, payload); |
9ba49bb5 CMN |
617 | } |
618 | ||
613d5eb9 PK |
619 | int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url) |
620 | { | |
621 | git_config *cfg; | |
622 | const char *val; | |
623 | ||
624 | assert(remote); | |
625 | ||
a71c27cc | 626 | if (!proxy_url || !remote->repo) |
613d5eb9 PK |
627 | return -1; |
628 | ||
629 | *proxy_url = NULL; | |
630 | ||
631 | if (git_repository_config__weakptr(&cfg, remote->repo) < 0) | |
632 | return -1; | |
633 | ||
634 | /* Go through the possible sources for proxy configuration, from most specific | |
635 | * to least specific. */ | |
636 | ||
637 | /* remote.<name>.proxy config setting */ | |
638 | if (remote->name && 0 != *(remote->name)) { | |
639 | git_buf buf = GIT_BUF_INIT; | |
640 | ||
641 | if (git_buf_printf(&buf, "remote.%s.proxy", remote->name) < 0) | |
642 | return -1; | |
643 | ||
644 | if (!git_config_get_string(&val, cfg, git_buf_cstr(&buf)) && | |
645 | val && ('\0' != *val)) { | |
646 | git_buf_free(&buf); | |
647 | ||
648 | *proxy_url = git__strdup(val); | |
649 | GITERR_CHECK_ALLOC(*proxy_url); | |
650 | return 0; | |
651 | } | |
652 | ||
653 | git_buf_free(&buf); | |
654 | } | |
655 | ||
656 | /* http.proxy config setting */ | |
657 | if (!git_config_get_string(&val, cfg, "http.proxy") && | |
658 | val && ('\0' != *val)) { | |
659 | *proxy_url = git__strdup(val); | |
660 | GITERR_CHECK_ALLOC(*proxy_url); | |
661 | return 0; | |
662 | } | |
663 | ||
664 | /* HTTP_PROXY / HTTPS_PROXY environment variables */ | |
665 | val = use_ssl ? getenv("HTTPS_PROXY") : getenv("HTTP_PROXY"); | |
666 | ||
667 | if (val && ('\0' != *val)) { | |
668 | *proxy_url = git__strdup(val); | |
669 | GITERR_CHECK_ALLOC(*proxy_url); | |
670 | return 0; | |
671 | } | |
672 | ||
673 | return 0; | |
674 | } | |
675 | ||
d8488457 CMN |
676 | static int store_refs(git_remote_head *head, void *payload) |
677 | { | |
678 | git_vector *refs = (git_vector *)payload; | |
679 | ||
680 | return git_vector_insert(refs, head); | |
681 | } | |
682 | ||
683 | static int dwim_refspecs(git_vector *refspecs, git_vector *refs) | |
684 | { | |
685 | git_buf buf = GIT_BUF_INIT; | |
686 | git_refspec *spec; | |
687 | size_t i, j, pos; | |
688 | git_remote_head key; | |
689 | ||
690 | const char* formatters[] = { | |
691 | GIT_REFS_DIR "%s", | |
692 | GIT_REFS_TAGS_DIR "%s", | |
693 | GIT_REFS_HEADS_DIR "%s", | |
694 | NULL | |
695 | }; | |
696 | ||
697 | git_vector_foreach(refspecs, i, spec) { | |
698 | if (spec->dwim) | |
699 | continue; | |
700 | ||
701 | /* shorthand on the lhs */ | |
702 | if (git__prefixcmp(spec->src, GIT_REFS_DIR)) { | |
703 | for (j = 0; formatters[j]; j++) { | |
704 | git_buf_clear(&buf); | |
705 | if (git_buf_printf(&buf, formatters[j], spec->src) < 0) | |
706 | return -1; | |
707 | ||
708 | key.name = (char *) git_buf_cstr(&buf); | |
709 | if (!git_vector_search(&pos, refs, &key)) { | |
710 | /* we found something to match the shorthand, set src to that */ | |
711 | git__free(spec->src); | |
712 | spec->src = git_buf_detach(&buf); | |
713 | } | |
714 | } | |
715 | } | |
716 | ||
717 | if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) { | |
718 | /* if it starts with "remotes" then we just prepend "refs/" */ | |
719 | if (!git__prefixcmp(spec->dst, "remotes/")) { | |
720 | git_buf_puts(&buf, GIT_REFS_DIR); | |
721 | } else { | |
722 | git_buf_puts(&buf, GIT_REFS_HEADS_DIR); | |
723 | } | |
724 | ||
725 | if (git_buf_puts(&buf, spec->dst) < 0) | |
726 | return -1; | |
727 | ||
728 | git__free(spec->dst); | |
729 | spec->dst = git_buf_detach(&buf); | |
730 | } | |
731 | ||
732 | spec->dwim = 1; | |
733 | } | |
734 | ||
9c5d4b2e | 735 | git_buf_free(&buf); |
d8488457 CMN |
736 | return 0; |
737 | } | |
738 | ||
739 | static int remote_head_cmp(const void *_a, const void *_b) | |
740 | { | |
741 | const git_remote_head *a = (git_remote_head *) _a; | |
742 | const git_remote_head *b = (git_remote_head *) _b; | |
743 | ||
744 | return git__strcmp_cb(a->name, b->name); | |
745 | } | |
746 | ||
216863c4 BS |
747 | int git_remote_download( |
748 | git_remote *remote, | |
7d222e13 | 749 | git_transfer_progress_callback progress_cb, |
216863c4 | 750 | void *progress_payload) |
48a65a07 | 751 | { |
95057b85 | 752 | int error; |
d8488457 | 753 | git_vector refs; |
95057b85 | 754 | |
1e3b8ed5 | 755 | assert(remote); |
4bef3565 | 756 | |
d8488457 CMN |
757 | if (git_vector_init(&refs, 16, remote_head_cmp) < 0) |
758 | return -1; | |
759 | ||
760 | if (git_remote_ls(remote, store_refs, &refs) < 0) { | |
761 | return -1; | |
762 | } | |
763 | ||
764 | error = dwim_refspecs(&remote->refspecs, &refs); | |
765 | git_vector_free(&refs); | |
766 | if (error < 0) | |
767 | return -1; | |
768 | ||
95057b85 | 769 | if ((error = git_fetch_negotiate(remote)) < 0) |
4376f7f6 | 770 | return error; |
95057b85 | 771 | |
1e3b8ed5 | 772 | return git_fetch_download_pack(remote, progress_cb, progress_payload); |
48a65a07 CMN |
773 | } |
774 | ||
b0f6e45d ET |
775 | static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src) |
776 | { | |
777 | unsigned int i; | |
778 | git_remote_head *remote_ref; | |
779 | ||
780 | assert(update_heads && fetchspec_src); | |
781 | ||
782 | *out = NULL; | |
7d4b65f6 | 783 | |
784 | git_vector_foreach(update_heads, i, remote_ref) { | |
785 | if (strcmp(remote_ref->name, fetchspec_src) == 0) { | |
786 | *out = remote_ref; | |
787 | break; | |
b0f6e45d ET |
788 | } |
789 | } | |
790 | ||
791 | return 0; | |
792 | } | |
793 | ||
4330ab26 | 794 | static int remote_head_for_ref(git_remote_head **out, git_refspec *spec, git_vector *update_heads, git_reference *ref) |
b0f6e45d ET |
795 | { |
796 | git_reference *resolved_ref = NULL; | |
797 | git_reference *tracking_ref = NULL; | |
798 | git_buf remote_name = GIT_BUF_INIT; | |
799 | int error = 0; | |
800 | ||
4330ab26 | 801 | assert(out && spec && ref); |
b0f6e45d ET |
802 | |
803 | *out = NULL; | |
804 | ||
805 | if ((error = git_reference_resolve(&resolved_ref, ref)) < 0 || | |
806 | (!git_reference_is_branch(resolved_ref)) || | |
a258d8e3 | 807 | (error = git_branch_upstream(&tracking_ref, resolved_ref)) < 0 || |
4330ab26 | 808 | (error = git_refspec_transform_l(&remote_name, spec, git_reference_name(tracking_ref))) < 0) { |
605da51a | 809 | /* Not an error if HEAD is unborn or no tracking branch */ |
b0f6e45d ET |
810 | if (error == GIT_ENOTFOUND) |
811 | error = 0; | |
812 | ||
813 | goto cleanup; | |
814 | } | |
815 | ||
816 | error = remote_head_for_fetchspec_src(out, update_heads, git_buf_cstr(&remote_name)); | |
817 | ||
818 | cleanup: | |
819 | git_reference_free(tracking_ref); | |
820 | git_reference_free(resolved_ref); | |
821 | git_buf_free(&remote_name); | |
822 | return error; | |
823 | } | |
824 | ||
4330ab26 | 825 | static int git_remote_write_fetchhead(git_remote *remote, git_refspec *spec, git_vector *update_heads) |
b0f6e45d | 826 | { |
b0f6e45d ET |
827 | git_reference *head_ref = NULL; |
828 | git_fetchhead_ref *fetchhead_ref; | |
829 | git_remote_head *remote_ref, *merge_remote_ref; | |
830 | git_vector fetchhead_refs; | |
831 | bool include_all_fetchheads; | |
832 | unsigned int i = 0; | |
833 | int error = 0; | |
834 | ||
835 | assert(remote); | |
836 | ||
404eadb0 CMN |
837 | /* no heads, nothing to do */ |
838 | if (update_heads->length == 0) | |
839 | return 0; | |
840 | ||
b0f6e45d ET |
841 | if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0) |
842 | return -1; | |
843 | ||
844 | /* Iff refspec is * (but not subdir slash star), include tags */ | |
845 | include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0); | |
846 | ||
847 | /* Determine what to merge: if refspec was a wildcard, just use HEAD */ | |
848 | if (git_refspec_is_wildcard(spec)) { | |
849 | if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 || | |
4330ab26 | 850 | (error = remote_head_for_ref(&merge_remote_ref, spec, update_heads, head_ref)) < 0) |
b0f6e45d ET |
851 | goto cleanup; |
852 | } else { | |
853 | /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */ | |
854 | if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0) | |
855 | goto cleanup; | |
856 | } | |
857 | ||
858 | /* Create the FETCH_HEAD file */ | |
7d4b65f6 | 859 | git_vector_foreach(update_heads, i, remote_ref) { |
b0f6e45d ET |
860 | int merge_this_fetchhead = (merge_remote_ref == remote_ref); |
861 | ||
862 | if (!include_all_fetchheads && | |
863 | !git_refspec_src_matches(spec, remote_ref->name) && | |
864 | !merge_this_fetchhead) | |
865 | continue; | |
866 | ||
867 | if (git_fetchhead_ref_create(&fetchhead_ref, | |
868 | &remote_ref->oid, | |
869 | merge_this_fetchhead, | |
870 | remote_ref->name, | |
871 | git_remote_url(remote)) < 0) | |
872 | goto cleanup; | |
873 | ||
874 | if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0) | |
875 | goto cleanup; | |
876 | } | |
877 | ||
878 | git_fetchhead_write(remote->repo, &fetchhead_refs); | |
879 | ||
880 | cleanup: | |
881 | for (i = 0; i < fetchhead_refs.length; ++i) | |
882 | git_fetchhead_ref_free(fetchhead_refs.contents[i]); | |
883 | ||
884 | git_vector_free(&fetchhead_refs); | |
885 | git_reference_free(head_ref); | |
886 | ||
887 | return error; | |
888 | } | |
889 | ||
4330ab26 | 890 | static int update_tips_for_spec(git_remote *remote, git_refspec *spec, git_vector *refs) |
441f57c2 | 891 | { |
a37ddf7e | 892 | int error = 0, autotag; |
517bda19 | 893 | unsigned int i = 0; |
97769280 | 894 | git_buf refname = GIT_BUF_INIT; |
f184836b | 895 | git_oid old; |
a37ddf7e | 896 | git_odb *odb; |
441f57c2 CMN |
897 | git_remote_head *head; |
898 | git_reference *ref; | |
a37ddf7e | 899 | git_refspec tagspec; |
4330ab26 | 900 | git_vector update_heads; |
441f57c2 | 901 | |
4bef3565 VM |
902 | assert(remote); |
903 | ||
acd17006 | 904 | if (git_repository_odb__weakptr(&odb, remote->repo) < 0) |
a37ddf7e CMN |
905 | return -1; |
906 | ||
3230a44f | 907 | if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) |
a37ddf7e CMN |
908 | return -1; |
909 | ||
41fb1ca0 | 910 | /* Make a copy of the transport's refs */ |
4330ab26 | 911 | if (git_vector_init(&update_heads, 16, NULL) < 0) |
41fb1ca0 | 912 | return -1; |
585a2eb7 | 913 | |
4330ab26 CMN |
914 | for (; i < refs->length; ++i) { |
915 | head = git_vector_get(refs, i); | |
9063be1f | 916 | autotag = 0; |
517bda19 | 917 | |
a37ddf7e CMN |
918 | /* Ignore malformed ref names (which also saves us from tag^{} */ |
919 | if (!git_reference_is_valid_name(head->name)) | |
920 | continue; | |
921 | ||
d8488457 | 922 | if (git_refspec_src_matches(spec, head->name) && spec->dst) { |
a37ddf7e CMN |
923 | if (git_refspec_transform_r(&refname, spec, head->name) < 0) |
924 | goto on_error; | |
925 | } else if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_NONE) { | |
3230a44f CMN |
926 | |
927 | if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_ALL) | |
928 | autotag = 1; | |
a37ddf7e CMN |
929 | |
930 | if (!git_refspec_src_matches(&tagspec, head->name)) | |
931 | continue; | |
932 | ||
933 | git_buf_clear(&refname); | |
934 | if (git_buf_puts(&refname, head->name) < 0) | |
935 | goto on_error; | |
936 | } else { | |
937 | continue; | |
938 | } | |
939 | ||
940 | if (autotag && !git_odb_exists(odb, &head->oid)) | |
941 | continue; | |
f184836b | 942 | |
b0f6e45d ET |
943 | if (git_vector_insert(&update_heads, head) < 0) |
944 | goto on_error; | |
945 | ||
2508cc66 | 946 | error = git_reference_name_to_id(&old, remote->repo, refname.ptr); |
904b67e6 | 947 | if (error < 0 && error != GIT_ENOTFOUND) |
f184836b CMN |
948 | goto on_error; |
949 | ||
904b67e6 | 950 | if (error == GIT_ENOTFOUND) |
f184836b CMN |
951 | memset(&old, 0, GIT_OID_RAWSZ); |
952 | ||
b7f167da | 953 | if (!git_oid__cmp(&old, &head->oid)) |
f184836b | 954 | continue; |
441f57c2 | 955 | |
a37ddf7e | 956 | /* In autotag mode, don't overwrite any locally-existing tags */ |
2508cc66 | 957 | error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag); |
a37ddf7e | 958 | if (error < 0 && error != GIT_EEXISTS) |
944d250f | 959 | goto on_error; |
39157563 CMN |
960 | |
961 | git_reference_free(ref); | |
f184836b | 962 | |
b3aaa7a7 | 963 | if (remote->callbacks.update_tips != NULL) { |
df705148 | 964 | if (remote->callbacks.update_tips(refname.ptr, &old, &head->oid, remote->callbacks.payload) < 0) |
f184836b CMN |
965 | goto on_error; |
966 | } | |
441f57c2 CMN |
967 | } |
968 | ||
b0f6e45d | 969 | if (git_remote_update_fetchhead(remote) && |
4330ab26 | 970 | (error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0) |
b0f6e45d ET |
971 | goto on_error; |
972 | ||
b0f6e45d | 973 | git_vector_free(&update_heads); |
a37ddf7e | 974 | git_refspec__free(&tagspec); |
97769280 | 975 | git_buf_free(&refname); |
f184836b CMN |
976 | return 0; |
977 | ||
978 | on_error: | |
b0f6e45d | 979 | git_vector_free(&update_heads); |
a37ddf7e | 980 | git_refspec__free(&tagspec); |
f184836b CMN |
981 | git_buf_free(&refname); |
982 | return -1; | |
97769280 | 983 | |
441f57c2 CMN |
984 | } |
985 | ||
4330ab26 CMN |
986 | int git_remote_update_tips(git_remote *remote) |
987 | { | |
505b5d0c | 988 | git_refspec *spec, tagspec; |
4330ab26 | 989 | git_vector refs; |
505b5d0c | 990 | int error; |
4330ab26 CMN |
991 | size_t i; |
992 | ||
505b5d0c CMN |
993 | |
994 | if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) | |
995 | return -1; | |
996 | ||
4330ab26 CMN |
997 | if (git_vector_init(&refs, 16, NULL) < 0) |
998 | return -1; | |
999 | ||
505b5d0c CMN |
1000 | if ((error = git_remote_ls(remote, store_refs, &refs)) < 0) |
1001 | goto out; | |
1002 | ||
1003 | if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) { | |
1004 | error = update_tips_for_spec(remote, &tagspec, &refs); | |
1005 | goto out; | |
1006 | } | |
4330ab26 CMN |
1007 | |
1008 | git_vector_foreach(&remote->refspecs, i, spec) { | |
1009 | if (spec->push) | |
1010 | continue; | |
1011 | ||
505b5d0c CMN |
1012 | if ((error = update_tips_for_spec(remote, spec, &refs)) < 0) |
1013 | goto out; | |
4330ab26 CMN |
1014 | } |
1015 | ||
505b5d0c CMN |
1016 | out: |
1017 | git_refspec__free(&tagspec); | |
4330ab26 | 1018 | git_vector_free(&refs); |
505b5d0c | 1019 | return error; |
4330ab26 CMN |
1020 | } |
1021 | ||
6ac3b707 CMN |
1022 | int git_remote_connected(git_remote *remote) |
1023 | { | |
4bef3565 | 1024 | assert(remote); |
41fb1ca0 PK |
1025 | |
1026 | if (!remote->transport || !remote->transport->is_connected) | |
1027 | return 0; | |
1028 | ||
1029 | /* Ask the transport if it's connected. */ | |
613d5eb9 | 1030 | return remote->transport->is_connected(remote->transport); |
6ac3b707 CMN |
1031 | } |
1032 | ||
f0d2ddbb CMN |
1033 | void git_remote_stop(git_remote *remote) |
1034 | { | |
613d5eb9 PK |
1035 | assert(remote); |
1036 | ||
1037 | if (remote->transport && remote->transport->cancel) | |
41fb1ca0 | 1038 | remote->transport->cancel(remote->transport); |
f0d2ddbb CMN |
1039 | } |
1040 | ||
4cf01e9a CMN |
1041 | void git_remote_disconnect(git_remote *remote) |
1042 | { | |
4bef3565 VM |
1043 | assert(remote); |
1044 | ||
41fb1ca0 PK |
1045 | if (git_remote_connected(remote)) |
1046 | remote->transport->close(remote->transport); | |
4cf01e9a CMN |
1047 | } |
1048 | ||
9c82357b CMN |
1049 | void git_remote_free(git_remote *remote) |
1050 | { | |
4330ab26 | 1051 | git_refspec *spec; |
4330ab26 CMN |
1052 | size_t i; |
1053 | ||
2aae2188 CMN |
1054 | if (remote == NULL) |
1055 | return; | |
1056 | ||
42ea35c0 MS |
1057 | if (remote->transport != NULL) { |
1058 | git_remote_disconnect(remote); | |
1059 | ||
1060 | remote->transport->free(remote->transport); | |
1061 | remote->transport = NULL; | |
1062 | } | |
1063 | ||
1064 | git_vector_free(&remote->refs); | |
1065 | ||
4330ab26 CMN |
1066 | git_vector_foreach(&remote->refspecs, i, spec) { |
1067 | git_refspec__free(spec); | |
1068 | git__free(spec); | |
1069 | } | |
1070 | git_vector_free(&remote->refspecs); | |
1071 | ||
3286c408 | 1072 | git__free(remote->url); |
3ed4b501 | 1073 | git__free(remote->pushurl); |
3286c408 | 1074 | git__free(remote->name); |
3286c408 | 1075 | git__free(remote); |
9c82357b | 1076 | } |
8171998f CMN |
1077 | |
1078 | struct cb_data { | |
1079 | git_vector *list; | |
1080 | regex_t *preg; | |
1081 | }; | |
1082 | ||
a1abe66a | 1083 | static int remote_list_cb(const git_config_entry *entry, void *data_) |
8171998f CMN |
1084 | { |
1085 | struct cb_data *data = (struct cb_data *)data_; | |
1086 | size_t nmatch = 2; | |
1087 | regmatch_t pmatch[2]; | |
a1abe66a | 1088 | const char *name = entry->name; |
8171998f CMN |
1089 | |
1090 | if (!regexec(data->preg, name, nmatch, pmatch, 0)) { | |
1091 | char *remote_name = git__strndup(&name[pmatch[1].rm_so], pmatch[1].rm_eo - pmatch[1].rm_so); | |
4376f7f6 | 1092 | GITERR_CHECK_ALLOC(remote_name); |
8171998f | 1093 | |
4376f7f6 CMN |
1094 | if (git_vector_insert(data->list, remote_name) < 0) |
1095 | return -1; | |
8171998f CMN |
1096 | } |
1097 | ||
4376f7f6 | 1098 | return 0; |
8171998f CMN |
1099 | } |
1100 | ||
1101 | int git_remote_list(git_strarray *remotes_list, git_repository *repo) | |
1102 | { | |
1103 | git_config *cfg; | |
1104 | git_vector list; | |
1105 | regex_t preg; | |
1106 | struct cb_data data; | |
1107 | int error; | |
1108 | ||
4376f7f6 CMN |
1109 | if (git_repository_config__weakptr(&cfg, repo) < 0) |
1110 | return -1; | |
8171998f | 1111 | |
aec87f71 | 1112 | if (git_vector_init(&list, 4, git__strcmp_cb) < 0) |
4376f7f6 | 1113 | return -1; |
8171998f | 1114 | |
aec87f71 | 1115 | if (regcomp(&preg, "^remote\\.(.*)\\.(push)?url$", REG_EXTENDED) < 0) { |
4376f7f6 CMN |
1116 | giterr_set(GITERR_OS, "Remote catch regex failed to compile"); |
1117 | return -1; | |
1118 | } | |
8171998f CMN |
1119 | |
1120 | data.list = &list; | |
1121 | data.preg = &preg; | |
1122 | error = git_config_foreach(cfg, remote_list_cb, &data); | |
1123 | regfree(&preg); | |
4376f7f6 | 1124 | if (error < 0) { |
8171998f CMN |
1125 | size_t i; |
1126 | char *elem; | |
1127 | git_vector_foreach(&list, i, elem) { | |
2bc8fa02 | 1128 | git__free(elem); |
8171998f CMN |
1129 | } |
1130 | ||
1131 | git_vector_free(&list); | |
e4607392 RB |
1132 | |
1133 | /* cb error is converted to GIT_EUSER by git_config_foreach */ | |
1134 | if (error == GIT_EUSER) | |
1135 | error = -1; | |
1136 | ||
8171998f CMN |
1137 | return error; |
1138 | } | |
1139 | ||
aec87f71 | 1140 | git_vector_uniq(&list, git__free); |
1141 | ||
8171998f CMN |
1142 | remotes_list->strings = (char **)list.contents; |
1143 | remotes_list->count = list.length; | |
1144 | ||
4376f7f6 | 1145 | return 0; |
8171998f | 1146 | } |
a209a025 | 1147 | |
250b95b2 CMN |
1148 | void git_remote_check_cert(git_remote *remote, int check) |
1149 | { | |
1150 | assert(remote); | |
1151 | ||
1152 | remote->check_cert = check; | |
1153 | } | |
b3aaa7a7 | 1154 | |
9267ff58 | 1155 | int git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks) |
b3aaa7a7 CMN |
1156 | { |
1157 | assert(remote && callbacks); | |
1158 | ||
c7231c45 | 1159 | GITERR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); |
9267ff58 | 1160 | |
b3aaa7a7 | 1161 | memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks)); |
e03e71da | 1162 | |
41fb1ca0 PK |
1163 | if (remote->transport && remote->transport->set_callbacks) |
1164 | remote->transport->set_callbacks(remote->transport, | |
1165 | remote->callbacks.progress, | |
1166 | NULL, | |
df705148 | 1167 | remote->callbacks.payload); |
9267ff58 BS |
1168 | |
1169 | return 0; | |
41fb1ca0 PK |
1170 | } |
1171 | ||
091361f5 PK |
1172 | void git_remote_set_cred_acquire_cb( |
1173 | git_remote *remote, | |
59bccf33 BS |
1174 | git_cred_acquire_cb cred_acquire_cb, |
1175 | void *payload) | |
091361f5 PK |
1176 | { |
1177 | assert(remote); | |
1178 | ||
1179 | remote->cred_acquire_cb = cred_acquire_cb; | |
59bccf33 | 1180 | remote->cred_acquire_payload = payload; |
091361f5 PK |
1181 | } |
1182 | ||
41fb1ca0 PK |
1183 | int git_remote_set_transport(git_remote *remote, git_transport *transport) |
1184 | { | |
1185 | assert(remote && transport); | |
1186 | ||
c7231c45 | 1187 | GITERR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport"); |
10711769 | 1188 | |
e03e71da | 1189 | if (remote->transport) { |
41fb1ca0 PK |
1190 | giterr_set(GITERR_NET, "A transport is already bound to this remote"); |
1191 | return -1; | |
e03e71da | 1192 | } |
41fb1ca0 PK |
1193 | |
1194 | remote->transport = transport; | |
1195 | return 0; | |
b3aaa7a7 | 1196 | } |
f70e466f | 1197 | |
67dad09b | 1198 | const git_transfer_progress* git_remote_stats(git_remote *remote) |
d57c47dc BS |
1199 | { |
1200 | assert(remote); | |
1201 | return &remote->stats; | |
1202 | } | |
1203 | ||
f4a62c30 | 1204 | git_remote_autotag_option_t git_remote_autotag(git_remote *remote) |
f70e466f CMN |
1205 | { |
1206 | return remote->download_tags; | |
1207 | } | |
1208 | ||
f4a62c30 | 1209 | void git_remote_set_autotag(git_remote *remote, git_remote_autotag_option_t value) |
f70e466f CMN |
1210 | { |
1211 | remote->download_tags = value; | |
1212 | } | |
fcccf304 | 1213 | |
fcccf304 | 1214 | static int rename_remote_config_section( |
1215 | git_repository *repo, | |
1216 | const char *old_name, | |
1217 | const char *new_name) | |
1218 | { | |
1219 | git_buf old_section_name = GIT_BUF_INIT, | |
1220 | new_section_name = GIT_BUF_INIT; | |
1221 | int error = -1; | |
1222 | ||
1223 | if (git_buf_printf(&old_section_name, "remote.%s", old_name) < 0) | |
1224 | goto cleanup; | |
1225 | ||
1226 | if (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0) | |
1227 | goto cleanup; | |
1228 | ||
1229 | error = git_config_rename_section( | |
1230 | repo, | |
1231 | git_buf_cstr(&old_section_name), | |
1232 | git_buf_cstr(&new_section_name)); | |
1233 | ||
1234 | cleanup: | |
1235 | git_buf_free(&old_section_name); | |
1236 | git_buf_free(&new_section_name); | |
1237 | ||
1238 | return error; | |
1239 | } | |
1240 | ||
1241 | struct update_data | |
1242 | { | |
1243 | git_config *config; | |
1244 | const char *old_remote_name; | |
1245 | const char *new_remote_name; | |
1246 | }; | |
1247 | ||
1248 | static int update_config_entries_cb( | |
1249 | const git_config_entry *entry, | |
1250 | void *payload) | |
1251 | { | |
1252 | struct update_data *data = (struct update_data *)payload; | |
1253 | ||
1254 | if (strcmp(entry->value, data->old_remote_name)) | |
1255 | return 0; | |
1256 | ||
1257 | return git_config_set_string( | |
1258 | data->config, | |
1259 | entry->name, | |
1260 | data->new_remote_name); | |
1261 | } | |
1262 | ||
1263 | static int update_branch_remote_config_entry( | |
1264 | git_repository *repo, | |
1265 | const char *old_name, | |
1266 | const char *new_name) | |
1267 | { | |
1268 | git_config *config; | |
1269 | struct update_data data; | |
1270 | ||
1271 | if (git_repository_config__weakptr(&config, repo) < 0) | |
1272 | return -1; | |
1273 | ||
1274 | data.config = config; | |
1275 | data.old_remote_name = old_name; | |
1276 | data.new_remote_name = new_name; | |
1277 | ||
1278 | return git_config_foreach_match( | |
1279 | config, | |
1280 | "branch\\..+\\.remote", | |
1281 | update_config_entries_cb, &data); | |
1282 | } | |
1283 | ||
fcccf304 | 1284 | static int rename_one_remote_reference( |
56960b83 | 1285 | git_reference *reference, |
fcccf304 | 1286 | const char *old_remote_name, |
1287 | const char *new_remote_name) | |
1288 | { | |
6cfbbf7e | 1289 | int error = -1; |
fcccf304 | 1290 | git_buf new_name = GIT_BUF_INIT; |
fcccf304 | 1291 | |
1292 | if (git_buf_printf( | |
1293 | &new_name, | |
1294 | GIT_REFS_REMOTES_DIR "%s%s", | |
1295 | new_remote_name, | |
56960b83 | 1296 | reference->name + strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name)) < 0) |
fcccf304 | 1297 | return -1; |
1298 | ||
4e6e2ff2 | 1299 | error = git_reference_rename(NULL, reference, git_buf_cstr(&new_name), 0); |
d00d5464 | 1300 | git_reference_free(reference); |
fcccf304 | 1301 | |
fcccf304 | 1302 | git_buf_free(&new_name); |
1303 | return error; | |
1304 | } | |
1305 | ||
1306 | static int rename_remote_references( | |
1307 | git_repository *repo, | |
1308 | const char *old_name, | |
1309 | const char *new_name) | |
1310 | { | |
fcccf304 | 1311 | int error = -1; |
56960b83 | 1312 | git_reference *ref; |
9bd89d96 | 1313 | git_reference_iterator *iter; |
fcccf304 | 1314 | |
9bd89d96 | 1315 | if (git_reference_iterator_new(&iter, repo) < 0) |
56960b83 | 1316 | return -1; |
fcccf304 | 1317 | |
56960b83 | 1318 | while ((error = git_reference_next(&ref, iter)) == 0) { |
2f77d8f1 RB |
1319 | if (git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) { |
1320 | git_reference_free(ref); | |
9bd89d96 | 1321 | continue; |
2f77d8f1 | 1322 | } |
9bd89d96 | 1323 | |
56960b83 VM |
1324 | if ((error = rename_one_remote_reference(ref, old_name, new_name)) < 0) { |
1325 | git_reference_iterator_free(iter); | |
1326 | return error; | |
1327 | } | |
9bd89d96 CMN |
1328 | } |
1329 | ||
1330 | git_reference_iterator_free(iter); | |
fcccf304 | 1331 | |
56960b83 VM |
1332 | if (error == GIT_ITEROVER) |
1333 | return 0; | |
fcccf304 | 1334 | |
fcccf304 | 1335 | return error; |
1336 | } | |
1337 | ||
1338 | static int rename_fetch_refspecs( | |
1339 | git_remote *remote, | |
1340 | const char *new_name, | |
1341 | int (*callback)(const char *problematic_refspec, void *payload), | |
1342 | void *payload) | |
1343 | { | |
1344 | git_config *config; | |
4330ab26 | 1345 | git_buf base = GIT_BUF_INIT, var = GIT_BUF_INIT, val = GIT_BUF_INIT; |
1be680c4 | 1346 | const git_refspec *spec; |
4330ab26 | 1347 | size_t i; |
fcccf304 | 1348 | int error = -1; |
1349 | ||
4330ab26 CMN |
1350 | if (git_buf_printf(&base, "+refs/heads/*:refs/remotes/%s/*", remote->name) < 0) |
1351 | goto cleanup; | |
fcccf304 | 1352 | |
1be680c4 | 1353 | git_vector_foreach(&remote->refspecs, i, spec) { |
4330ab26 CMN |
1354 | if (spec->push) |
1355 | continue; | |
fcccf304 | 1356 | |
4330ab26 CMN |
1357 | /* Every refspec is a problem refspec for an in-memory remote */ |
1358 | if (!remote->name) { | |
1be680c4 | 1359 | if (callback(spec->string, payload) < 0) { |
4330ab26 CMN |
1360 | error = GIT_EUSER; |
1361 | goto cleanup; | |
1362 | } | |
fcccf304 | 1363 | |
4330ab26 CMN |
1364 | continue; |
1365 | } | |
fcccf304 | 1366 | |
4330ab26 | 1367 | /* Does the dst part of the refspec follow the extected standard format? */ |
1be680c4 CMN |
1368 | if (strcmp(git_buf_cstr(&base), spec->string)) { |
1369 | if (callback(spec->string, payload) < 0) { | |
4330ab26 CMN |
1370 | error = GIT_EUSER; |
1371 | goto cleanup; | |
1372 | } | |
fcccf304 | 1373 | |
4330ab26 CMN |
1374 | continue; |
1375 | } | |
fcccf304 | 1376 | |
4330ab26 CMN |
1377 | /* If we do want to move it to the new section */ |
1378 | if (git_buf_printf(&val, "+refs/heads/*:refs/remotes/%s/*", new_name) < 0) | |
fcccf304 | 1379 | goto cleanup; |
1380 | ||
4330ab26 CMN |
1381 | if (git_buf_printf(&var, "remote.%s.fetch", new_name) < 0) |
1382 | goto cleanup; | |
fcccf304 | 1383 | |
4330ab26 CMN |
1384 | if (git_repository_config__weakptr(&config, remote->repo) < 0) |
1385 | goto cleanup; | |
fcccf304 | 1386 | |
4330ab26 CMN |
1387 | if (git_config_set_string(config, git_buf_cstr(&var), git_buf_cstr(&val)) < 0) |
1388 | goto cleanup; | |
1389 | } | |
fcccf304 | 1390 | |
4330ab26 | 1391 | error = 0; |
fcccf304 | 1392 | |
1393 | cleanup: | |
4330ab26 CMN |
1394 | git_buf_free(&base); |
1395 | git_buf_free(&var); | |
1396 | git_buf_free(&val); | |
fcccf304 | 1397 | return error; |
1398 | } | |
1399 | ||
1400 | int git_remote_rename( | |
1401 | git_remote *remote, | |
1402 | const char *new_name, | |
df705148 | 1403 | git_remote_rename_problem_cb callback, |
fcccf304 | 1404 | void *payload) |
1405 | { | |
1406 | int error; | |
1407 | ||
1408 | assert(remote && new_name); | |
1409 | ||
c07b52df | 1410 | if (!remote->name) { |
79000951 BS |
1411 | giterr_set(GITERR_INVALID, "Can't rename an in-memory remote."); |
1412 | return GIT_EINVALIDSPEC; | |
1413 | } | |
1414 | ||
fcccf304 | 1415 | if ((error = ensure_remote_name_is_valid(new_name)) < 0) |
1416 | return error; | |
1417 | ||
a71c27cc BS |
1418 | if (remote->repo) { |
1419 | if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0) | |
fcccf304 | 1420 | return error; |
1421 | ||
a71c27cc BS |
1422 | if (!remote->name) { |
1423 | if ((error = rename_fetch_refspecs( | |
1424 | remote, | |
1425 | new_name, | |
1426 | callback, | |
1427 | payload)) < 0) | |
1428 | return error; | |
fcccf304 | 1429 | |
a71c27cc | 1430 | remote->name = git__strdup(new_name); |
fcccf304 | 1431 | |
c07b52df | 1432 | if (!remote->name) return 0; |
a71c27cc BS |
1433 | return git_remote_save(remote); |
1434 | } | |
fcccf304 | 1435 | |
a71c27cc BS |
1436 | if ((error = rename_remote_config_section( |
1437 | remote->repo, | |
1438 | remote->name, | |
1439 | new_name)) < 0) | |
1440 | return error; | |
fcccf304 | 1441 | |
a71c27cc BS |
1442 | if ((error = update_branch_remote_config_entry( |
1443 | remote->repo, | |
1444 | remote->name, | |
1445 | new_name)) < 0) | |
1446 | return error; | |
fcccf304 | 1447 | |
a71c27cc BS |
1448 | if ((error = rename_remote_references( |
1449 | remote->repo, | |
1450 | remote->name, | |
1451 | new_name)) < 0) | |
1452 | return error; | |
1453 | ||
1454 | if ((error = rename_fetch_refspecs( | |
1455 | remote, | |
1456 | new_name, | |
1457 | callback, | |
1458 | payload)) < 0) | |
1459 | return error; | |
1460 | } | |
fcccf304 | 1461 | |
1462 | git__free(remote->name); | |
1463 | remote->name = git__strdup(new_name); | |
1464 | ||
1465 | return 0; | |
1466 | } | |
b0f6e45d ET |
1467 | |
1468 | int git_remote_update_fetchhead(git_remote *remote) | |
1469 | { | |
1470 | return remote->update_fetchhead; | |
1471 | } | |
1472 | ||
1473 | void git_remote_set_update_fetchhead(git_remote *remote, int value) | |
1474 | { | |
1475 | remote->update_fetchhead = value; | |
1476 | } | |
2bca5b67 | 1477 | |
1478 | int git_remote_is_valid_name( | |
1479 | const char *remote_name) | |
1480 | { | |
1481 | git_buf buf = GIT_BUF_INIT; | |
1482 | git_refspec refspec; | |
1483 | int error = -1; | |
1484 | ||
1485 | if (!remote_name || *remote_name == '\0') | |
1486 | return 0; | |
1487 | ||
1488 | git_buf_printf(&buf, "refs/heads/test:refs/remotes/%s/test", remote_name); | |
1489 | error = git_refspec__parse(&refspec, git_buf_cstr(&buf), true); | |
1490 | ||
1491 | git_buf_free(&buf); | |
1492 | git_refspec__free(&refspec); | |
1493 | ||
1494 | giterr_clear(); | |
1495 | return error == 0; | |
1496 | } | |
4330ab26 CMN |
1497 | |
1498 | git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname) | |
1499 | { | |
1500 | git_refspec *spec; | |
1501 | size_t i; | |
1502 | ||
1503 | git_vector_foreach(&remote->refspecs, i, spec) { | |
1504 | if (spec->push) | |
1505 | continue; | |
1506 | ||
1507 | if (git_refspec_src_matches(spec, refname)) | |
1508 | return spec; | |
1509 | } | |
1510 | ||
1511 | return NULL; | |
1512 | } | |
1513 | ||
1514 | git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname) | |
1515 | { | |
1516 | git_refspec *spec; | |
1517 | size_t i; | |
1518 | ||
1519 | git_vector_foreach(&remote->refspecs, i, spec) { | |
1520 | if (spec->push) | |
1521 | continue; | |
1522 | ||
1523 | if (git_refspec_dst_matches(spec, refname)) | |
1524 | return spec; | |
1525 | } | |
1526 | ||
1527 | return NULL; | |
1528 | } | |
1529 | ||
1530 | void git_remote_clear_refspecs(git_remote *remote) | |
1531 | { | |
1532 | git_refspec *spec; | |
4330ab26 CMN |
1533 | size_t i; |
1534 | ||
1535 | git_vector_foreach(&remote->refspecs, i, spec) { | |
1536 | git_refspec__free(spec); | |
1be680c4 | 1537 | git__free(spec); |
4330ab26 CMN |
1538 | } |
1539 | git_vector_clear(&remote->refspecs); | |
4330ab26 CMN |
1540 | } |
1541 | ||
bc6374ea | 1542 | int git_remote_add_fetch(git_remote *remote, const char *refspec) |
4330ab26 CMN |
1543 | { |
1544 | return add_refspec(remote, refspec, true); | |
1545 | } | |
1546 | ||
bc6374ea | 1547 | int git_remote_add_push(git_remote *remote, const char *refspec) |
4330ab26 CMN |
1548 | { |
1549 | return add_refspec(remote, refspec, false); | |
1550 | } | |
bc6374ea | 1551 | |
66566516 | 1552 | static int copy_refspecs(git_strarray *array, git_remote *remote, unsigned int push) |
bc6374ea CMN |
1553 | { |
1554 | size_t i; | |
1555 | git_vector refspecs; | |
1556 | git_refspec *spec; | |
1557 | char *dup; | |
1558 | ||
1559 | if (git_vector_init(&refspecs, remote->refspecs.length, NULL) < 0) | |
1560 | return -1; | |
1561 | ||
1562 | git_vector_foreach(&remote->refspecs, i, spec) { | |
1563 | if (spec->push != push) | |
1564 | continue; | |
1565 | ||
1be680c4 | 1566 | if ((dup = git__strdup(spec->string)) == NULL) |
bc6374ea | 1567 | goto on_error; |
bc6374ea CMN |
1568 | |
1569 | if (git_vector_insert(&refspecs, dup) < 0) { | |
1570 | git__free(dup); | |
1571 | goto on_error; | |
1572 | } | |
1573 | } | |
1574 | ||
1575 | array->strings = (char **)refspecs.contents; | |
1576 | array->count = refspecs.length; | |
1577 | ||
1578 | return 0; | |
1579 | ||
1580 | on_error: | |
1581 | git_vector_foreach(&refspecs, i, dup) | |
1582 | git__free(dup); | |
1583 | git_vector_free(&refspecs); | |
1584 | ||
1585 | return -1; | |
1586 | } | |
1587 | ||
1588 | int git_remote_get_fetch_refspecs(git_strarray *array, git_remote *remote) | |
1589 | { | |
1590 | return copy_refspecs(array, remote, false); | |
1591 | } | |
1592 | ||
1593 | int git_remote_get_push_refspecs(git_strarray *array, git_remote *remote) | |
1594 | { | |
1595 | return copy_refspecs(array, remote, true); | |
1596 | } | |
1ffd0806 CMN |
1597 | |
1598 | size_t git_remote_refspec_count(git_remote *remote) | |
1599 | { | |
1600 | return remote->refspecs.length; | |
1601 | } | |
1602 | ||
1603 | const git_refspec *git_remote_get_refspec(git_remote *remote, size_t n) | |
1604 | { | |
1605 | return git_vector_get(&remote->refspecs, n); | |
1606 | } | |
1607 | ||
1608 | int git_remote_remove_refspec(git_remote *remote, size_t n) | |
1609 | { | |
1610 | git_refspec *spec; | |
1611 | ||
1612 | assert(remote); | |
1613 | ||
1614 | spec = git_vector_get(&remote->refspecs, n); | |
1615 | if (spec) { | |
1616 | git_refspec__free(spec); | |
1617 | git__free(spec); | |
1618 | } | |
1619 | ||
1620 | return git_vector_remove(&remote->refspecs, n); | |
1621 | } |