]>
Commit | Line | Data |
---|---|---|
bfc9ca59 RB |
1 | /* |
2 | * Copyright (C) 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 "git2/config.h" | |
10 | #include "git2/types.h" | |
11 | #include "git2/repository.h" | |
12 | #include "git2/index.h" | |
13 | #include "git2/submodule.h" | |
14 | #include "buffer.h" | |
bfc9ca59 RB |
15 | #include "vector.h" |
16 | #include "posix.h" | |
17 | #include "config_file.h" | |
18 | #include "config.h" | |
19 | #include "repository.h" | |
aa13bf05 RB |
20 | #include "submodule.h" |
21 | #include "tree.h" | |
22 | #include "iterator.h" | |
23 | ||
24 | #define GIT_MODULES_FILE ".gitmodules" | |
bfc9ca59 | 25 | |
95dfb031 RB |
26 | static git_cvar_map _sm_update_map[] = { |
27 | {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT}, | |
28 | {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, | |
aa13bf05 RB |
29 | {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}, |
30 | {GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE}, | |
bfc9ca59 RB |
31 | }; |
32 | ||
95dfb031 | 33 | static git_cvar_map _sm_ignore_map[] = { |
aa13bf05 | 34 | {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}, |
95dfb031 | 35 | {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, |
aa13bf05 RB |
36 | {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, |
37 | {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, | |
bfc9ca59 RB |
38 | }; |
39 | ||
72ee0787 | 40 | static kh_inline khint_t str_hash_no_trailing_slash(const char *s) |
bfc9ca59 | 41 | { |
01fed0a8 | 42 | khint_t h; |
bfc9ca59 | 43 | |
01fed0a8 | 44 | for (h = 0; *s; ++s) |
5f4a61ae | 45 | if (s[1] != '\0' || *s != '/') |
01fed0a8 | 46 | h = (h << 5) - h + *s; |
bfc9ca59 | 47 | |
01fed0a8 | 48 | return h; |
bfc9ca59 RB |
49 | } |
50 | ||
72ee0787 | 51 | static kh_inline int str_equal_no_trailing_slash(const char *a, const char *b) |
bfc9ca59 | 52 | { |
01fed0a8 RB |
53 | size_t alen = a ? strlen(a) : 0; |
54 | size_t blen = b ? strlen(b) : 0; | |
bfc9ca59 | 55 | |
5f4a61ae | 56 | if (alen > 0 && a[alen - 1] == '/') |
bfc9ca59 | 57 | alen--; |
5f4a61ae | 58 | if (blen > 0 && b[blen - 1] == '/') |
bfc9ca59 RB |
59 | blen--; |
60 | ||
01fed0a8 | 61 | return (alen == blen && strncmp(a, b, alen) == 0); |
bfc9ca59 RB |
62 | } |
63 | ||
aa13bf05 RB |
64 | __KHASH_IMPL( |
65 | str, static kh_inline, const char *, void *, 1, | |
66 | str_hash_no_trailing_slash, str_equal_no_trailing_slash); | |
67 | ||
5f4a61ae RB |
68 | static int load_submodule_config(git_repository *repo, bool force); |
69 | static git_config_file *open_gitmodules(git_repository *, bool, const git_oid *); | |
70 | static int lookup_head_remote(git_buf *url, git_repository *repo); | |
71 | static int submodule_get(git_submodule **, git_repository *, const char *, const char *); | |
72 | static void submodule_release(git_submodule *sm, int decr); | |
73 | static int submodule_load_from_index(git_repository *, const git_index_entry *); | |
74 | static int submodule_load_from_head(git_repository*, const char*, const git_oid*); | |
75 | static int submodule_load_from_config(const char *, const char *, void *); | |
76 | static int submodule_load_from_wd_lite(git_submodule *, const char *, void *); | |
77 | static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool); | |
78 | static void submodule_mode_mismatch(git_repository *, const char *, unsigned int); | |
79 | static int submodule_index_status(unsigned int *status, git_submodule *sm); | |
80 | static int submodule_wd_status(unsigned int *status, git_submodule *sm); | |
aa13bf05 RB |
81 | |
82 | static int submodule_cmp(const void *a, const void *b) | |
83 | { | |
84 | return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name); | |
85 | } | |
86 | ||
87 | static int submodule_config_key_trunc_puts(git_buf *key, const char *suffix) | |
88 | { | |
89 | ssize_t idx = git_buf_rfind(key, '.'); | |
90 | git_buf_truncate(key, (size_t)(idx + 1)); | |
91 | return git_buf_puts(key, suffix); | |
92 | } | |
93 | ||
94 | /* | |
95 | * PUBLIC APIS | |
96 | */ | |
97 | ||
98 | int git_submodule_lookup( | |
99 | git_submodule **sm_ptr, /* NULL if user only wants to test existence */ | |
100 | git_repository *repo, | |
101 | const char *name) /* trailing slash is allowed */ | |
102 | { | |
103 | int error; | |
104 | khiter_t pos; | |
105 | ||
106 | assert(repo && name); | |
107 | ||
108 | if ((error = load_submodule_config(repo, false)) < 0) | |
109 | return error; | |
110 | ||
111 | pos = git_strmap_lookup_index(repo->submodules, name); | |
112 | ||
113 | if (!git_strmap_valid_index(repo->submodules, pos)) { | |
114 | error = GIT_ENOTFOUND; | |
115 | ||
116 | /* check if a plausible submodule exists at path */ | |
117 | if (git_repository_workdir(repo)) { | |
118 | git_buf path = GIT_BUF_INIT; | |
119 | ||
120 | if (git_buf_joinpath(&path, git_repository_workdir(repo), name) < 0) | |
121 | return -1; | |
122 | ||
123 | if (git_path_contains_dir(&path, DOT_GIT)) | |
124 | error = GIT_EEXISTS; | |
125 | ||
126 | git_buf_free(&path); | |
127 | } | |
128 | ||
129 | return error; | |
130 | } | |
131 | ||
132 | if (sm_ptr) | |
133 | *sm_ptr = git_strmap_value_at(repo->submodules, pos); | |
134 | ||
135 | return 0; | |
136 | } | |
01fed0a8 | 137 | |
aa13bf05 RB |
138 | int git_submodule_foreach( |
139 | git_repository *repo, | |
140 | int (*callback)(git_submodule *sm, const char *name, void *payload), | |
141 | void *payload) | |
142 | { | |
143 | int error; | |
144 | git_submodule *sm; | |
145 | git_vector seen = GIT_VECTOR_INIT; | |
146 | seen._cmp = submodule_cmp; | |
147 | ||
148 | assert(repo && callback); | |
149 | ||
150 | if ((error = load_submodule_config(repo, false)) < 0) | |
151 | return error; | |
152 | ||
153 | git_strmap_foreach_value(repo->submodules, sm, { | |
154 | /* Usually the following will not come into play - it just prevents | |
155 | * us from issuing a callback twice for a submodule where the name | |
156 | * and path are not the same. | |
157 | */ | |
158 | if (sm->refcount > 1) { | |
159 | if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND) | |
160 | continue; | |
161 | if ((error = git_vector_insert(&seen, sm)) < 0) | |
162 | break; | |
163 | } | |
164 | ||
5f4a61ae RB |
165 | if (callback(sm, sm->name, payload)) { |
166 | error = GIT_EUSER; | |
aa13bf05 | 167 | break; |
5f4a61ae | 168 | } |
aa13bf05 RB |
169 | }); |
170 | ||
171 | git_vector_free(&seen); | |
172 | ||
173 | return error; | |
174 | } | |
175 | ||
176 | void git_submodule_config_free(git_repository *repo) | |
177 | { | |
178 | git_strmap *smcfg; | |
179 | git_submodule *sm; | |
180 | ||
181 | assert(repo); | |
182 | ||
183 | smcfg = repo->submodules; | |
184 | repo->submodules = NULL; | |
185 | ||
186 | if (smcfg == NULL) | |
187 | return; | |
188 | ||
189 | git_strmap_foreach_value(smcfg, sm, { | |
190 | submodule_release(sm,1); | |
191 | }); | |
192 | git_strmap_free(smcfg); | |
193 | } | |
194 | ||
195 | int git_submodule_add_setup( | |
196 | git_submodule **submodule, | |
197 | git_repository *repo, | |
198 | const char *url, | |
199 | const char *path, | |
200 | int use_gitlink) | |
201 | { | |
202 | int error = 0; | |
203 | git_config_file *mods = NULL; | |
204 | git_submodule *sm; | |
205 | git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT; | |
206 | git_repository_init_options initopt; | |
207 | git_repository *subrepo = NULL; | |
208 | ||
209 | assert(repo && url && path); | |
210 | ||
211 | /* see if there is already an entry for this submodule */ | |
212 | ||
213 | if (git_submodule_lookup(&sm, repo, path) < 0) | |
214 | giterr_clear(); | |
215 | else { | |
216 | giterr_set(GITERR_SUBMODULE, | |
217 | "Attempt to add a submodule that already exists"); | |
218 | return GIT_EEXISTS; | |
219 | } | |
220 | ||
221 | /* resolve parameters */ | |
222 | ||
223 | if (url[0] == '.' && (url[1] == '/' || (url[1] == '.' && url[2] == '/'))) { | |
224 | if (!(error = lookup_head_remote(&real_url, repo))) | |
225 | error = git_path_apply_relative(&real_url, url); | |
226 | } else if (strchr(url, ':') != NULL || url[0] == '/') { | |
227 | error = git_buf_sets(&real_url, url); | |
228 | } else { | |
229 | giterr_set(GITERR_SUBMODULE, "Invalid format for submodule URL"); | |
230 | error = -1; | |
231 | } | |
232 | if (error) | |
233 | goto cleanup; | |
234 | ||
235 | /* validate and normalize path */ | |
236 | ||
237 | if (git__prefixcmp(path, git_repository_workdir(repo)) == 0) | |
238 | path += strlen(git_repository_workdir(repo)); | |
239 | ||
240 | if (git_path_root(path) >= 0) { | |
241 | giterr_set(GITERR_SUBMODULE, "Submodule path must be a relative path"); | |
242 | error = -1; | |
243 | goto cleanup; | |
244 | } | |
245 | ||
246 | /* update .gitmodules */ | |
247 | ||
248 | if ((mods = open_gitmodules(repo, true, NULL)) == NULL) { | |
249 | giterr_set(GITERR_SUBMODULE, | |
250 | "Adding submodules to a bare repository is not supported (for now)"); | |
251 | return -1; | |
252 | } | |
253 | ||
254 | if ((error = git_buf_printf(&name, "submodule.%s.path", path)) < 0 || | |
255 | (error = git_config_file_set_string(mods, name.ptr, path)) < 0) | |
256 | goto cleanup; | |
257 | ||
258 | if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 || | |
259 | (error = git_config_file_set_string(mods, name.ptr, real_url.ptr)) < 0) | |
260 | goto cleanup; | |
261 | ||
262 | git_buf_clear(&name); | |
263 | ||
264 | /* init submodule repository and add origin remote as needed */ | |
265 | ||
266 | error = git_buf_joinpath(&name, git_repository_workdir(repo), path); | |
267 | if (error < 0) | |
268 | goto cleanup; | |
269 | ||
270 | /* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a | |
271 | * gitlink in the sub-repo workdir directory to that repository | |
272 | * | |
273 | * Old style: sub-repo goes directly into repo/<name>/.git/ | |
274 | */ | |
275 | ||
276 | memset(&initopt, 0, sizeof(initopt)); | |
277 | initopt.flags = GIT_REPOSITORY_INIT_MKPATH | | |
278 | GIT_REPOSITORY_INIT_NO_REINIT; | |
279 | initopt.origin_url = real_url.ptr; | |
280 | ||
281 | if (git_path_exists(name.ptr) && | |
282 | git_path_contains(&name, DOT_GIT)) | |
283 | { | |
284 | /* repo appears to already exist - reinit? */ | |
285 | } | |
286 | else if (use_gitlink) { | |
287 | git_buf repodir = GIT_BUF_INIT; | |
288 | ||
289 | error = git_buf_join_n( | |
290 | &repodir, '/', 3, git_repository_path(repo), "modules", path); | |
291 | if (error < 0) | |
292 | goto cleanup; | |
293 | ||
294 | initopt.workdir_path = name.ptr; | |
295 | initopt.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; | |
296 | ||
297 | error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); | |
298 | ||
299 | git_buf_free(&repodir); | |
300 | } | |
301 | else { | |
302 | error = git_repository_init_ext(&subrepo, name.ptr, &initopt); | |
303 | } | |
304 | if (error < 0) | |
305 | goto cleanup; | |
306 | ||
307 | /* add submodule to hash and "reload" it */ | |
308 | ||
0c8858de RB |
309 | if (!(error = submodule_get(&sm, repo, path, NULL)) && |
310 | !(error = git_submodule_reload(sm))) | |
311 | error = git_submodule_init(sm, false); | |
aa13bf05 RB |
312 | |
313 | cleanup: | |
314 | if (submodule != NULL) | |
315 | *submodule = !error ? sm : NULL; | |
316 | ||
317 | if (mods != NULL) | |
318 | git_config_file_free(mods); | |
319 | git_repository_free(subrepo); | |
320 | git_buf_free(&real_url); | |
321 | git_buf_free(&name); | |
322 | ||
323 | return error; | |
324 | } | |
325 | ||
326 | int git_submodule_add_finalize(git_submodule *sm) | |
327 | { | |
328 | int error; | |
329 | git_index *index; | |
330 | ||
331 | assert(sm); | |
332 | ||
333 | if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 || | |
334 | (error = git_index_add(index, GIT_MODULES_FILE, 0)) < 0) | |
335 | return error; | |
336 | ||
5f4a61ae | 337 | return git_submodule_add_to_index(sm, true); |
aa13bf05 RB |
338 | } |
339 | ||
5f4a61ae | 340 | int git_submodule_add_to_index(git_submodule *sm, int write_index) |
aa13bf05 RB |
341 | { |
342 | int error; | |
343 | git_repository *repo, *sm_repo; | |
344 | git_index *index; | |
345 | git_buf path = GIT_BUF_INIT; | |
346 | git_commit *head; | |
347 | git_index_entry entry; | |
348 | struct stat st; | |
349 | ||
350 | assert(sm); | |
351 | ||
352 | repo = sm->owner; | |
353 | ||
5f4a61ae RB |
354 | /* force reload of wd OID by git_submodule_open */ |
355 | sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID; | |
356 | ||
aa13bf05 RB |
357 | if ((error = git_repository_index__weakptr(&index, repo)) < 0 || |
358 | (error = git_buf_joinpath( | |
359 | &path, git_repository_workdir(repo), sm->path)) < 0 || | |
360 | (error = git_submodule_open(&sm_repo, sm)) < 0) | |
361 | goto cleanup; | |
362 | ||
363 | /* read stat information for submodule working directory */ | |
364 | if (p_stat(path.ptr, &st) < 0) { | |
365 | giterr_set(GITERR_SUBMODULE, | |
366 | "Cannot add submodule without working directory"); | |
367 | error = -1; | |
368 | goto cleanup; | |
369 | } | |
5f4a61ae | 370 | entry.path = sm->path; |
aa13bf05 RB |
371 | git_index__init_entry_from_stat(&st, &entry); |
372 | ||
373 | /* calling git_submodule_open will have set sm->wd_oid if possible */ | |
374 | if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) { | |
375 | giterr_set(GITERR_SUBMODULE, | |
376 | "Cannot add submodule without HEAD to index"); | |
377 | error = -1; | |
378 | goto cleanup; | |
379 | } | |
380 | git_oid_cpy(&entry.oid, &sm->wd_oid); | |
381 | ||
382 | if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0) | |
383 | goto cleanup; | |
384 | ||
385 | entry.ctime.seconds = git_commit_time(head); | |
386 | entry.ctime.nanoseconds = 0; | |
387 | entry.mtime.seconds = git_commit_time(head); | |
388 | entry.mtime.nanoseconds = 0; | |
389 | ||
390 | git_commit_free(head); | |
391 | ||
5f4a61ae | 392 | /* add it */ |
aa13bf05 RB |
393 | error = git_index_add2(index, &entry); |
394 | ||
5f4a61ae RB |
395 | /* write it, if requested */ |
396 | if (!error && write_index) { | |
397 | error = git_index_write(index); | |
398 | ||
399 | if (!error) | |
400 | git_oid_cpy(&sm->index_oid, &sm->wd_oid); | |
401 | } | |
402 | ||
aa13bf05 RB |
403 | cleanup: |
404 | git_repository_free(sm_repo); | |
405 | git_buf_free(&path); | |
406 | return error; | |
407 | } | |
408 | ||
409 | int git_submodule_save(git_submodule *submodule) | |
410 | { | |
411 | int error = 0; | |
412 | git_config_file *mods; | |
413 | git_buf key = GIT_BUF_INIT; | |
414 | ||
415 | assert(submodule); | |
416 | ||
417 | mods = open_gitmodules(submodule->owner, true, NULL); | |
418 | if (!mods) { | |
419 | giterr_set(GITERR_SUBMODULE, | |
420 | "Adding submodules to a bare repository is not supported (for now)"); | |
421 | return -1; | |
422 | } | |
423 | ||
424 | if ((error = git_buf_printf(&key, "submodule.%s.", submodule->name)) < 0) | |
425 | goto cleanup; | |
426 | ||
427 | /* save values for path, url, update, ignore, fetchRecurseSubmodules */ | |
428 | ||
429 | if ((error = submodule_config_key_trunc_puts(&key, "path")) < 0 || | |
430 | (error = git_config_file_set_string(mods, key.ptr, submodule->path)) < 0) | |
431 | goto cleanup; | |
432 | ||
433 | if ((error = submodule_config_key_trunc_puts(&key, "url")) < 0 || | |
434 | (error = git_config_file_set_string(mods, key.ptr, submodule->url)) < 0) | |
435 | goto cleanup; | |
436 | ||
437 | if (!(error = submodule_config_key_trunc_puts(&key, "update")) && | |
438 | submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT) | |
439 | { | |
440 | const char *val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ? | |
441 | NULL : _sm_update_map[submodule->update].str_match; | |
442 | error = git_config_file_set_string(mods, key.ptr, val); | |
443 | } | |
444 | if (error < 0) | |
445 | goto cleanup; | |
446 | ||
447 | if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) && | |
448 | submodule->ignore != GIT_SUBMODULE_IGNORE_DEFAULT) | |
449 | { | |
450 | const char *val = (submodule->ignore == GIT_SUBMODULE_IGNORE_NONE) ? | |
451 | NULL : _sm_ignore_map[submodule->ignore].str_match; | |
452 | error = git_config_file_set_string(mods, key.ptr, val); | |
453 | } | |
454 | if (error < 0) | |
455 | goto cleanup; | |
456 | ||
457 | if ((error = submodule_config_key_trunc_puts( | |
458 | &key, "fetchRecurseSubmodules")) < 0 || | |
459 | (error = git_config_file_set_string( | |
460 | mods, key.ptr, submodule->fetch_recurse ? "true" : "false")) < 0) | |
461 | goto cleanup; | |
462 | ||
463 | /* update internal defaults */ | |
464 | ||
465 | submodule->ignore_default = submodule->ignore; | |
466 | submodule->update_default = submodule->update; | |
467 | submodule->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; | |
468 | ||
469 | cleanup: | |
470 | if (mods != NULL) | |
471 | git_config_file_free(mods); | |
472 | git_buf_free(&key); | |
473 | ||
474 | return error; | |
475 | } | |
476 | ||
477 | git_repository *git_submodule_owner(git_submodule *submodule) | |
478 | { | |
479 | assert(submodule); | |
480 | return submodule->owner; | |
481 | } | |
482 | ||
483 | const char *git_submodule_name(git_submodule *submodule) | |
484 | { | |
485 | assert(submodule); | |
486 | return submodule->name; | |
487 | } | |
488 | ||
489 | const char *git_submodule_path(git_submodule *submodule) | |
490 | { | |
491 | assert(submodule); | |
492 | return submodule->path; | |
493 | } | |
494 | ||
495 | const char *git_submodule_url(git_submodule *submodule) | |
496 | { | |
497 | assert(submodule); | |
498 | return submodule->url; | |
499 | } | |
500 | ||
501 | int git_submodule_set_url(git_submodule *submodule, const char *url) | |
502 | { | |
503 | assert(submodule && url); | |
504 | ||
505 | git__free(submodule->url); | |
506 | ||
507 | submodule->url = git__strdup(url); | |
508 | GITERR_CHECK_ALLOC(submodule->url); | |
509 | ||
510 | return 0; | |
511 | } | |
512 | ||
5f4a61ae | 513 | const git_oid *git_submodule_index_oid(git_submodule *submodule) |
aa13bf05 RB |
514 | { |
515 | assert(submodule); | |
516 | ||
517 | if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) | |
518 | return &submodule->index_oid; | |
519 | else | |
520 | return NULL; | |
521 | } | |
522 | ||
523 | const git_oid *git_submodule_head_oid(git_submodule *submodule) | |
524 | { | |
525 | assert(submodule); | |
526 | ||
527 | if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) | |
528 | return &submodule->head_oid; | |
529 | else | |
530 | return NULL; | |
531 | } | |
532 | ||
533 | const git_oid *git_submodule_wd_oid(git_submodule *submodule) | |
534 | { | |
535 | assert(submodule); | |
536 | ||
537 | if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { | |
538 | git_repository *subrepo; | |
539 | ||
540 | /* calling submodule open grabs the HEAD OID if possible */ | |
541 | if (!git_submodule_open(&subrepo, submodule)) | |
542 | git_repository_free(subrepo); | |
5f4a61ae RB |
543 | else |
544 | giterr_clear(); | |
aa13bf05 RB |
545 | } |
546 | ||
547 | if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) | |
548 | return &submodule->wd_oid; | |
549 | else | |
550 | return NULL; | |
551 | } | |
552 | ||
553 | git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule) | |
554 | { | |
555 | assert(submodule); | |
556 | return submodule->ignore; | |
557 | } | |
558 | ||
559 | git_submodule_ignore_t git_submodule_set_ignore( | |
560 | git_submodule *submodule, git_submodule_ignore_t ignore) | |
561 | { | |
562 | git_submodule_ignore_t old; | |
563 | ||
564 | assert(submodule); | |
565 | ||
566 | if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT) | |
567 | ignore = submodule->ignore_default; | |
568 | ||
569 | old = submodule->ignore; | |
570 | submodule->ignore = ignore; | |
571 | return old; | |
572 | } | |
573 | ||
574 | git_submodule_update_t git_submodule_update(git_submodule *submodule) | |
575 | { | |
576 | assert(submodule); | |
577 | return submodule->update; | |
578 | } | |
579 | ||
580 | git_submodule_update_t git_submodule_set_update( | |
581 | git_submodule *submodule, git_submodule_update_t update) | |
582 | { | |
583 | git_submodule_update_t old; | |
584 | ||
585 | assert(submodule); | |
586 | ||
587 | if (update == GIT_SUBMODULE_UPDATE_DEFAULT) | |
588 | update = submodule->update_default; | |
589 | ||
590 | old = submodule->update; | |
591 | submodule->update = update; | |
592 | return old; | |
593 | } | |
594 | ||
595 | int git_submodule_init(git_submodule *submodule, int overwrite) | |
596 | { | |
597 | int error; | |
598 | ||
599 | /* write "submodule.NAME.url" */ | |
600 | ||
601 | if (!submodule->url) { | |
602 | giterr_set(GITERR_SUBMODULE, | |
603 | "No URL configured for submodule '%s'", submodule->name); | |
604 | return -1; | |
605 | } | |
606 | ||
607 | error = submodule_update_config( | |
608 | submodule, "url", submodule->url, overwrite != 0, false); | |
609 | if (error < 0) | |
610 | return error; | |
611 | ||
612 | /* write "submodule.NAME.update" if not default */ | |
613 | ||
614 | if (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) | |
615 | error = submodule_update_config( | |
616 | submodule, "update", NULL, (overwrite != 0), false); | |
617 | else if (submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT) | |
618 | error = submodule_update_config( | |
619 | submodule, "update", | |
620 | _sm_update_map[submodule->update].str_match, | |
621 | (overwrite != 0), false); | |
622 | ||
623 | return error; | |
624 | } | |
625 | ||
626 | int git_submodule_sync(git_submodule *submodule) | |
627 | { | |
628 | if (!submodule->url) { | |
629 | giterr_set(GITERR_SUBMODULE, | |
630 | "No URL configured for submodule '%s'", submodule->name); | |
631 | return -1; | |
632 | } | |
633 | ||
634 | /* copy URL over to config only if it already exists */ | |
635 | ||
636 | return submodule_update_config( | |
637 | submodule, "url", submodule->url, true, true); | |
638 | } | |
639 | ||
640 | int git_submodule_open( | |
641 | git_repository **subrepo, | |
642 | git_submodule *submodule) | |
643 | { | |
644 | int error; | |
645 | git_buf path = GIT_BUF_INIT; | |
646 | git_repository *repo; | |
647 | const char *workdir; | |
648 | ||
649 | assert(submodule && subrepo); | |
650 | ||
651 | repo = submodule->owner; | |
652 | workdir = git_repository_workdir(repo); | |
653 | ||
654 | if (!workdir) { | |
655 | giterr_set(GITERR_REPOSITORY, | |
656 | "Cannot open submodule repository in a bare repo"); | |
657 | return GIT_ENOTFOUND; | |
658 | } | |
659 | ||
660 | if ((submodule->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) { | |
661 | giterr_set(GITERR_REPOSITORY, | |
662 | "Cannot open submodule repository that is not checked out"); | |
663 | return GIT_ENOTFOUND; | |
664 | } | |
665 | ||
666 | if (git_buf_joinpath(&path, workdir, submodule->path) < 0) | |
667 | return -1; | |
668 | ||
669 | error = git_repository_open(subrepo, path.ptr); | |
670 | ||
671 | git_buf_free(&path); | |
672 | ||
673 | /* if we have opened the submodule successfully, let's grab the HEAD OID */ | |
674 | if (!error && !(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { | |
675 | if (!git_reference_name_to_oid( | |
676 | &submodule->wd_oid, *subrepo, GIT_HEAD_FILE)) | |
677 | submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; | |
678 | else | |
679 | giterr_clear(); | |
680 | } | |
681 | ||
682 | return error; | |
683 | } | |
684 | ||
685 | int git_submodule_reload_all(git_repository *repo) | |
686 | { | |
687 | assert(repo); | |
688 | return load_submodule_config(repo, true); | |
689 | } | |
690 | ||
691 | int git_submodule_reload(git_submodule *submodule) | |
692 | { | |
693 | git_repository *repo; | |
694 | git_index *index; | |
695 | int pos, error; | |
696 | git_tree *head; | |
697 | git_config_file *mods; | |
698 | ||
699 | assert(submodule); | |
700 | ||
701 | /* refresh index data */ | |
702 | ||
703 | repo = submodule->owner; | |
704 | if (git_repository_index__weakptr(&index, repo) < 0) | |
705 | return -1; | |
706 | ||
5f4a61ae RB |
707 | submodule->flags = submodule->flags & |
708 | ~(GIT_SUBMODULE_STATUS_IN_INDEX | | |
709 | GIT_SUBMODULE_STATUS__INDEX_OID_VALID); | |
710 | ||
aa13bf05 RB |
711 | pos = git_index_find(index, submodule->path); |
712 | if (pos >= 0) { | |
713 | git_index_entry *entry = git_index_get(index, pos); | |
714 | ||
5f4a61ae RB |
715 | if (S_ISGITLINK(entry->mode)) { |
716 | if ((error = submodule_load_from_index(repo, entry)) < 0) | |
717 | return error; | |
718 | } else { | |
719 | submodule_mode_mismatch( | |
720 | repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE); | |
721 | } | |
aa13bf05 RB |
722 | } |
723 | ||
724 | /* refresh HEAD tree data */ | |
725 | ||
726 | if (!(error = git_repository_head_tree(&head, repo))) { | |
727 | git_tree_entry *te; | |
728 | ||
729 | submodule->flags = submodule->flags & | |
730 | ~(GIT_SUBMODULE_STATUS_IN_HEAD | | |
731 | GIT_SUBMODULE_STATUS__HEAD_OID_VALID); | |
732 | ||
733 | if (!(error = git_tree_entry_bypath(&te, head, submodule->path))) { | |
5f4a61ae RB |
734 | |
735 | if (S_ISGITLINK(te->attr)) { | |
736 | error = submodule_load_from_head(repo, submodule->path, &te->oid); | |
737 | } else { | |
738 | submodule_mode_mismatch( | |
739 | repo, submodule->path, | |
740 | GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE); | |
741 | } | |
aa13bf05 RB |
742 | |
743 | git_tree_entry_free(te); | |
744 | } | |
745 | else if (error == GIT_ENOTFOUND) { | |
746 | giterr_clear(); | |
747 | error = 0; | |
748 | } | |
749 | ||
750 | git_tree_free(head); | |
751 | } | |
752 | ||
753 | if (error < 0) | |
754 | return error; | |
755 | ||
756 | /* refresh config data */ | |
757 | ||
758 | if ((mods = open_gitmodules(repo, false, NULL)) != NULL) { | |
759 | git_buf path = GIT_BUF_INIT; | |
760 | ||
761 | git_buf_sets(&path, "submodule\\."); | |
762 | git_buf_puts_escape_regex(&path, submodule->name); | |
763 | git_buf_puts(&path, ".*"); | |
764 | ||
765 | if (git_buf_oom(&path)) | |
766 | error = -1; | |
767 | else | |
768 | error = git_config_file_foreach_match( | |
769 | mods, path.ptr, submodule_load_from_config, repo); | |
770 | ||
771 | git_buf_free(&path); | |
0c8858de | 772 | git_config_file_free(mods); |
aa13bf05 RB |
773 | } |
774 | ||
5f4a61ae RB |
775 | if (error < 0) |
776 | return error; | |
777 | ||
778 | /* refresh wd data */ | |
779 | ||
780 | submodule->flags = submodule->flags & | |
781 | ~(GIT_SUBMODULE_STATUS_IN_WD | GIT_SUBMODULE_STATUS__WD_OID_VALID); | |
782 | ||
783 | error = submodule_load_from_wd_lite(submodule, submodule->path, NULL); | |
784 | ||
aa13bf05 RB |
785 | return error; |
786 | } | |
787 | ||
788 | int git_submodule_status( | |
789 | unsigned int *status, | |
790 | git_submodule *submodule) | |
791 | { | |
5f4a61ae RB |
792 | int error = 0; |
793 | unsigned int status_val; | |
794 | ||
aa13bf05 RB |
795 | assert(status && submodule); |
796 | ||
5f4a61ae | 797 | status_val = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(submodule->flags); |
0c8858de | 798 | |
5f4a61ae RB |
799 | if (submodule->ignore != GIT_SUBMODULE_IGNORE_ALL) { |
800 | if (!(error = submodule_index_status(&status_val, submodule))) | |
801 | error = submodule_wd_status(&status_val, submodule); | |
802 | } | |
aa13bf05 | 803 | |
5f4a61ae | 804 | *status = status_val; |
aa13bf05 | 805 | |
5f4a61ae | 806 | return error; |
aa13bf05 RB |
807 | } |
808 | ||
809 | /* | |
810 | * INTERNAL FUNCTIONS | |
811 | */ | |
812 | ||
813 | static git_submodule *submodule_alloc(git_repository *repo, const char *name) | |
bfc9ca59 | 814 | { |
0c8858de | 815 | git_submodule *sm; |
bfc9ca59 | 816 | |
0c8858de RB |
817 | if (!name || !strlen(name)) { |
818 | giterr_set(GITERR_SUBMODULE, "Invalid submodule name"); | |
bfc9ca59 RB |
819 | return NULL; |
820 | } | |
821 | ||
0c8858de RB |
822 | sm = git__calloc(1, sizeof(git_submodule)); |
823 | if (sm == NULL) | |
824 | goto fail; | |
825 | ||
826 | sm->path = sm->name = git__strdup(name); | |
827 | if (!sm->name) | |
828 | goto fail; | |
829 | ||
aa13bf05 | 830 | sm->owner = repo; |
0c8858de | 831 | sm->refcount = 1; |
aa13bf05 RB |
832 | |
833 | return sm; | |
0c8858de RB |
834 | |
835 | fail: | |
836 | submodule_release(sm, 0); | |
837 | return NULL; | |
aa13bf05 RB |
838 | } |
839 | ||
840 | static void submodule_release(git_submodule *sm, int decr) | |
841 | { | |
842 | if (!sm) | |
843 | return; | |
844 | ||
845 | sm->refcount -= decr; | |
846 | ||
847 | if (sm->refcount == 0) { | |
848 | if (sm->name != sm->path) { | |
849 | git__free(sm->path); | |
850 | sm->path = NULL; | |
851 | } | |
852 | ||
853 | git__free(sm->name); | |
854 | sm->name = NULL; | |
855 | ||
856 | git__free(sm->url); | |
857 | sm->url = NULL; | |
858 | ||
859 | sm->owner = NULL; | |
860 | ||
861 | git__free(sm); | |
862 | } | |
863 | } | |
864 | ||
0c8858de RB |
865 | static int submodule_get( |
866 | git_submodule **sm_ptr, | |
867 | git_repository *repo, | |
868 | const char *name, | |
869 | const char *alternate) | |
aa13bf05 RB |
870 | { |
871 | git_strmap *smcfg = repo->submodules; | |
872 | khiter_t pos; | |
873 | git_submodule *sm; | |
0c8858de | 874 | int error; |
aa13bf05 | 875 | |
0c8858de | 876 | assert(repo && name); |
aa13bf05 | 877 | |
0c8858de | 878 | pos = git_strmap_lookup_index(smcfg, name); |
aa13bf05 | 879 | |
0c8858de RB |
880 | if (!git_strmap_valid_index(smcfg, pos) && alternate) |
881 | pos = git_strmap_lookup_index(smcfg, alternate); | |
aa13bf05 | 882 | |
0c8858de RB |
883 | if (!git_strmap_valid_index(smcfg, pos)) { |
884 | sm = submodule_alloc(repo, name); | |
aa13bf05 | 885 | |
0c8858de RB |
886 | /* insert value at name - if another thread beats us to it, then use |
887 | * their record and release our own. | |
888 | */ | |
5f4a61ae | 889 | pos = kh_put(str, smcfg, sm->name, &error); |
0c8858de RB |
890 | |
891 | if (error < 0) { | |
892 | submodule_release(sm, 1); | |
893 | sm = NULL; | |
894 | } else if (error == 0) { | |
895 | submodule_release(sm, 1); | |
896 | sm = git_strmap_value_at(smcfg, pos); | |
897 | } else { | |
898 | git_strmap_set_value_at(smcfg, pos, sm); | |
899 | } | |
900 | } else { | |
901 | sm = git_strmap_value_at(smcfg, pos); | |
aa13bf05 RB |
902 | } |
903 | ||
0c8858de | 904 | *sm_ptr = sm; |
aa13bf05 | 905 | |
0c8858de | 906 | return (sm != NULL) ? 0 : -1; |
bfc9ca59 RB |
907 | } |
908 | ||
aa13bf05 RB |
909 | static int submodule_load_from_index( |
910 | git_repository *repo, const git_index_entry *entry) | |
bfc9ca59 | 911 | { |
0c8858de | 912 | git_submodule *sm; |
bfc9ca59 | 913 | |
0c8858de | 914 | if (submodule_get(&sm, repo, entry->path, NULL) < 0) |
aa13bf05 | 915 | return -1; |
bfc9ca59 | 916 | |
aa13bf05 RB |
917 | if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) { |
918 | sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES; | |
919 | return 0; | |
bfc9ca59 | 920 | } |
bfc9ca59 | 921 | |
aa13bf05 | 922 | sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX; |
01fed0a8 | 923 | |
aa13bf05 RB |
924 | git_oid_cpy(&sm->index_oid, &entry->oid); |
925 | sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID; | |
bfc9ca59 | 926 | |
0c8858de | 927 | return 0; |
aa13bf05 | 928 | } |
bfc9ca59 | 929 | |
aa13bf05 RB |
930 | static int submodule_load_from_head( |
931 | git_repository *repo, const char *path, const git_oid *oid) | |
932 | { | |
0c8858de | 933 | git_submodule *sm; |
bfc9ca59 | 934 | |
0c8858de | 935 | if (submodule_get(&sm, repo, path, NULL) < 0) |
aa13bf05 | 936 | return -1; |
bfc9ca59 | 937 | |
aa13bf05 | 938 | sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD; |
bfc9ca59 | 939 | |
aa13bf05 RB |
940 | git_oid_cpy(&sm->head_oid, oid); |
941 | sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID; | |
bfc9ca59 | 942 | |
0c8858de RB |
943 | return 0; |
944 | } | |
945 | ||
946 | static int submodule_config_error(const char *property, const char *value) | |
947 | { | |
948 | giterr_set(GITERR_INVALID, | |
949 | "Invalid value for submodule '%s' property: '%s'", property, value); | |
950 | return -1; | |
bfc9ca59 RB |
951 | } |
952 | ||
aa13bf05 | 953 | static int submodule_load_from_config( |
bfc9ca59 RB |
954 | const char *key, const char *value, void *data) |
955 | { | |
aa13bf05 RB |
956 | git_repository *repo = data; |
957 | git_strmap *smcfg = repo->submodules; | |
0c8858de | 958 | const char *namestart, *property, *alternate = NULL; |
bfc9ca59 RB |
959 | git_buf name = GIT_BUF_INIT; |
960 | git_submodule *sm; | |
bfc9ca59 | 961 | bool is_path; |
0c8858de | 962 | int error = 0; |
bfc9ca59 RB |
963 | |
964 | if (git__prefixcmp(key, "submodule.") != 0) | |
965 | return 0; | |
966 | ||
967 | namestart = key + strlen("submodule."); | |
968 | property = strrchr(namestart, '.'); | |
969 | if (property == NULL) | |
970 | return 0; | |
971 | property++; | |
aa13bf05 | 972 | is_path = (strcasecmp(property, "path") == 0); |
bfc9ca59 RB |
973 | |
974 | if (git_buf_set(&name, namestart, property - namestart - 1) < 0) | |
975 | return -1; | |
976 | ||
0c8858de RB |
977 | if (submodule_get(&sm, repo, name.ptr, is_path ? value : NULL) < 0) { |
978 | git_buf_free(&name); | |
979 | return -1; | |
980 | } | |
bfc9ca59 | 981 | |
aa13bf05 RB |
982 | sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; |
983 | ||
0c8858de RB |
984 | /* Only from config might we get differing names & paths. If so, then |
985 | * update the submodule and insert under the alternative key. | |
986 | */ | |
01fed0a8 | 987 | |
0c8858de RB |
988 | /* TODO: if case insensitive filesystem, then the following strcmps |
989 | * should be strcasecmp | |
990 | */ | |
01fed0a8 | 991 | |
0c8858de RB |
992 | if (strcmp(sm->name, name.ptr) != 0) { |
993 | alternate = sm->name = git_buf_detach(&name); | |
994 | } else if (is_path && value && strcmp(sm->path, value) != 0) { | |
995 | alternate = sm->path = git__strdup(value); | |
996 | if (!sm->path) | |
997 | error = -1; | |
bfc9ca59 | 998 | } |
0c8858de RB |
999 | if (alternate) { |
1000 | void *old_sm = NULL; | |
1001 | git_strmap_insert2(smcfg, alternate, sm, old_sm, error); | |
bfc9ca59 | 1002 | |
0c8858de RB |
1003 | if (error >= 0) |
1004 | sm->refcount++; /* inserted under a new key */ | |
1005 | ||
1006 | /* if we replaced an old module under this key, release the old one */ | |
1007 | if (old_sm && ((git_submodule *)old_sm) != sm) { | |
1008 | submodule_release(old_sm, 1); | |
1009 | /* TODO: log warning about multiple submodules with same path */ | |
1010 | } | |
bfc9ca59 RB |
1011 | } |
1012 | ||
0c8858de RB |
1013 | git_buf_free(&name); |
1014 | if (error < 0) | |
1015 | return error; | |
1016 | ||
aa13bf05 RB |
1017 | /* TODO: Look up path in index and if it is present but not a GITLINK |
1018 | * then this should be deleted (at least to match git's behavior) | |
1019 | */ | |
1020 | ||
bfc9ca59 RB |
1021 | if (is_path) |
1022 | return 0; | |
1023 | ||
1024 | /* copy other properties into submodule entry */ | |
aa13bf05 | 1025 | if (strcasecmp(property, "url") == 0) { |
0c8858de RB |
1026 | git__free(sm->url); |
1027 | sm->url = NULL; | |
1028 | ||
aa13bf05 | 1029 | if (value != NULL && (sm->url = git__strdup(value)) == NULL) |
0c8858de | 1030 | return -1; |
bfc9ca59 | 1031 | } |
aa13bf05 | 1032 | else if (strcasecmp(property, "update") == 0) { |
95dfb031 RB |
1033 | int val; |
1034 | if (git_config_lookup_map_value( | |
0c8858de RB |
1035 | _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) |
1036 | return submodule_config_error("update", value); | |
aa13bf05 | 1037 | sm->update_default = sm->update = (git_submodule_update_t)val; |
bfc9ca59 | 1038 | } |
aa13bf05 | 1039 | else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) { |
0c8858de RB |
1040 | if (git__parse_bool(&sm->fetch_recurse, value) < 0) |
1041 | return submodule_config_error("fetchRecurseSubmodules", value); | |
bfc9ca59 | 1042 | } |
aa13bf05 | 1043 | else if (strcasecmp(property, "ignore") == 0) { |
95dfb031 RB |
1044 | int val; |
1045 | if (git_config_lookup_map_value( | |
0c8858de RB |
1046 | _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) |
1047 | return submodule_config_error("ignore", value); | |
aa13bf05 | 1048 | sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val; |
bfc9ca59 RB |
1049 | } |
1050 | /* ignore other unknown submodule properties */ | |
1051 | ||
1052 | return 0; | |
bfc9ca59 RB |
1053 | } |
1054 | ||
aa13bf05 RB |
1055 | static int submodule_load_from_wd_lite( |
1056 | git_submodule *sm, const char *name, void *payload) | |
1057 | { | |
1058 | git_repository *repo = git_submodule_owner(sm); | |
1059 | git_buf path = GIT_BUF_INIT; | |
1060 | ||
1061 | GIT_UNUSED(name); | |
1062 | GIT_UNUSED(payload); | |
1063 | ||
1064 | if (git_buf_joinpath(&path, git_repository_workdir(repo), sm->path) < 0) | |
1065 | return -1; | |
1066 | ||
1067 | if (git_path_isdir(path.ptr)) | |
1068 | sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; | |
1069 | ||
1070 | if (git_path_contains(&path, DOT_GIT)) | |
1071 | sm->flags |= GIT_SUBMODULE_STATUS_IN_WD; | |
1072 | ||
1073 | git_buf_free(&path); | |
1074 | ||
1075 | return 0; | |
1076 | } | |
1077 | ||
5f4a61ae RB |
1078 | static void submodule_mode_mismatch( |
1079 | git_repository *repo, const char *path, unsigned int flag) | |
1080 | { | |
1081 | khiter_t pos = git_strmap_lookup_index(repo->submodules, path); | |
1082 | ||
1083 | if (git_strmap_valid_index(repo->submodules, pos)) { | |
1084 | git_submodule *sm = git_strmap_value_at(repo->submodules, pos); | |
1085 | ||
1086 | sm->flags |= flag; | |
1087 | } | |
1088 | } | |
1089 | ||
aa13bf05 RB |
1090 | static int load_submodule_config_from_index( |
1091 | git_repository *repo, git_oid *gitmodules_oid) | |
bfc9ca59 RB |
1092 | { |
1093 | int error; | |
aa13bf05 RB |
1094 | git_iterator *i; |
1095 | const git_index_entry *entry; | |
bfc9ca59 | 1096 | |
aa13bf05 RB |
1097 | if ((error = git_iterator_for_index(&i, repo)) < 0) |
1098 | return error; | |
bfc9ca59 | 1099 | |
aa13bf05 | 1100 | error = git_iterator_current(i, &entry); |
bfc9ca59 | 1101 | |
aa13bf05 | 1102 | while (!error && entry != NULL) { |
bfc9ca59 | 1103 | |
bfc9ca59 | 1104 | if (S_ISGITLINK(entry->mode)) { |
aa13bf05 RB |
1105 | error = submodule_load_from_index(repo, entry); |
1106 | if (error < 0) | |
1107 | break; | |
5f4a61ae RB |
1108 | } else { |
1109 | submodule_mode_mismatch( | |
1110 | repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE); | |
1111 | ||
1112 | if (strcmp(entry->path, GIT_MODULES_FILE) == 0) | |
1113 | git_oid_cpy(gitmodules_oid, &entry->oid); | |
1114 | } | |
aa13bf05 RB |
1115 | |
1116 | error = git_iterator_advance(i, &entry); | |
bfc9ca59 RB |
1117 | } |
1118 | ||
aa13bf05 RB |
1119 | git_iterator_free(i); |
1120 | ||
1121 | return error; | |
1122 | } | |
1123 | ||
1124 | static int load_submodule_config_from_head( | |
1125 | git_repository *repo, git_oid *gitmodules_oid) | |
1126 | { | |
1127 | int error; | |
1128 | git_tree *head; | |
1129 | git_iterator *i; | |
1130 | const git_index_entry *entry; | |
1131 | ||
1132 | if ((error = git_repository_head_tree(&head, repo)) < 0) | |
1133 | return error; | |
1134 | ||
1135 | if ((error = git_iterator_for_tree(&i, repo, head)) < 0) { | |
1136 | git_tree_free(head); | |
1137 | return error; | |
1138 | } | |
1139 | ||
1140 | error = git_iterator_current(i, &entry); | |
1141 | ||
1142 | while (!error && entry != NULL) { | |
1143 | ||
1144 | if (S_ISGITLINK(entry->mode)) { | |
1145 | error = submodule_load_from_head(repo, entry->path, &entry->oid); | |
1146 | if (error < 0) | |
1147 | break; | |
5f4a61ae RB |
1148 | } else { |
1149 | submodule_mode_mismatch( | |
1150 | repo, entry->path, GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE); | |
1151 | ||
1152 | if (strcmp(entry->path, GIT_MODULES_FILE) == 0 && | |
1153 | git_oid_iszero(gitmodules_oid)) | |
1154 | git_oid_cpy(gitmodules_oid, &entry->oid); | |
1155 | } | |
aa13bf05 RB |
1156 | |
1157 | error = git_iterator_advance(i, &entry); | |
1158 | } | |
1159 | ||
1160 | git_iterator_free(i); | |
1161 | git_tree_free(head); | |
1162 | ||
1163 | return error; | |
1164 | } | |
1165 | ||
1166 | static git_config_file *open_gitmodules( | |
1167 | git_repository *repo, | |
1168 | bool okay_to_create, | |
1169 | const git_oid *gitmodules_oid) | |
1170 | { | |
1171 | const char *workdir = git_repository_workdir(repo); | |
1172 | git_buf path = GIT_BUF_INIT; | |
1173 | git_config_file *mods = NULL; | |
1174 | ||
1175 | if (workdir != NULL) { | |
1176 | if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0) | |
1177 | return NULL; | |
1178 | ||
1179 | if (okay_to_create || git_path_isfile(path.ptr)) { | |
1180 | /* git_config_file__ondisk should only fail if OOM */ | |
1181 | if (git_config_file__ondisk(&mods, path.ptr) < 0) | |
0c8858de | 1182 | mods = NULL; |
aa13bf05 | 1183 | /* open should only fail here if the file is malformed */ |
0c8858de | 1184 | else if (git_config_file_open(mods) < 0) { |
aa13bf05 RB |
1185 | git_config_file_free(mods); |
1186 | mods = NULL; | |
1187 | } | |
bfc9ca59 | 1188 | } |
bfc9ca59 RB |
1189 | } |
1190 | ||
aa13bf05 RB |
1191 | if (!mods && gitmodules_oid && !git_oid_iszero(gitmodules_oid)) { |
1192 | /* TODO: Retrieve .gitmodules content from ODB */ | |
1193 | ||
1194 | /* Should we actually do this? Core git does not, but it means you | |
1195 | * can't really get much information about submodules on bare repos. | |
1196 | */ | |
1197 | } | |
1198 | ||
0c8858de RB |
1199 | git_buf_free(&path); |
1200 | ||
aa13bf05 RB |
1201 | return mods; |
1202 | } | |
1203 | ||
1204 | static int load_submodule_config(git_repository *repo, bool force) | |
1205 | { | |
1206 | int error; | |
1207 | git_oid gitmodules_oid; | |
1208 | git_buf path = GIT_BUF_INIT; | |
1209 | git_config_file *mods = NULL; | |
1210 | ||
1211 | if (repo->submodules && !force) | |
1212 | return 0; | |
1213 | ||
1214 | memset(&gitmodules_oid, 0, sizeof(gitmodules_oid)); | |
1215 | ||
1216 | /* Submodule data is kept in a hashtable keyed by both name and path. | |
1217 | * These are usually the same, but that is not guaranteed. | |
1218 | */ | |
1219 | if (!repo->submodules) { | |
1220 | repo->submodules = git_strmap_alloc(); | |
1221 | GITERR_CHECK_ALLOC(repo->submodules); | |
bfc9ca59 RB |
1222 | } |
1223 | ||
aa13bf05 RB |
1224 | /* add submodule information from index */ |
1225 | ||
1226 | if ((error = load_submodule_config_from_index(repo, &gitmodules_oid)) < 0) | |
1227 | goto cleanup; | |
1228 | ||
1229 | /* add submodule information from HEAD */ | |
1230 | ||
1231 | if ((error = load_submodule_config_from_head(repo, &gitmodules_oid)) < 0) | |
1232 | goto cleanup; | |
1233 | ||
1234 | /* add submodule information from .gitmodules */ | |
1235 | ||
1236 | if ((mods = open_gitmodules(repo, false, &gitmodules_oid)) != NULL) | |
1237 | error = git_config_file_foreach(mods, submodule_load_from_config, repo); | |
1238 | ||
1239 | if (error != 0) | |
1240 | goto cleanup; | |
1241 | ||
1242 | /* shallow scan submodules in work tree */ | |
bfc9ca59 | 1243 | |
aa13bf05 RB |
1244 | if (!git_repository_is_bare(repo)) |
1245 | error = git_submodule_foreach(repo, submodule_load_from_wd_lite, NULL); | |
bfc9ca59 RB |
1246 | |
1247 | cleanup: | |
aa13bf05 RB |
1248 | git_buf_free(&path); |
1249 | ||
bfc9ca59 RB |
1250 | if (mods != NULL) |
1251 | git_config_file_free(mods); | |
aa13bf05 | 1252 | |
bfc9ca59 | 1253 | if (error) |
aa13bf05 RB |
1254 | git_submodule_config_free(repo); |
1255 | ||
bfc9ca59 RB |
1256 | return error; |
1257 | } | |
1258 | ||
aa13bf05 | 1259 | static int lookup_head_remote(git_buf *url, git_repository *repo) |
bfc9ca59 | 1260 | { |
aa13bf05 RB |
1261 | int error; |
1262 | git_config *cfg; | |
1263 | git_reference *head = NULL, *remote = NULL; | |
1264 | const char *tgt, *scan; | |
1265 | git_buf key = GIT_BUF_INIT; | |
1266 | ||
1267 | /* 1. resolve HEAD -> refs/heads/BRANCH | |
1268 | * 2. lookup config branch.BRANCH.remote -> ORIGIN | |
1269 | * 3. lookup remote.ORIGIN.url | |
1270 | */ | |
bfc9ca59 | 1271 | |
aa13bf05 RB |
1272 | if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) |
1273 | return error; | |
bfc9ca59 | 1274 | |
aa13bf05 RB |
1275 | if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) { |
1276 | giterr_set(GITERR_SUBMODULE, | |
1277 | "Cannot resolve relative URL when HEAD cannot be resolved"); | |
1278 | error = GIT_ENOTFOUND; | |
1279 | goto cleanup; | |
1280 | } | |
bfc9ca59 | 1281 | |
aa13bf05 RB |
1282 | if (git_reference_type(head) != GIT_REF_SYMBOLIC) { |
1283 | giterr_set(GITERR_SUBMODULE, | |
1284 | "Cannot resolve relative URL when HEAD is not symbolic"); | |
1285 | error = GIT_ENOTFOUND; | |
1286 | goto cleanup; | |
1287 | } | |
1288 | ||
1289 | if ((error = git_branch_tracking(&remote, head)) < 0) | |
1290 | goto cleanup; | |
1291 | ||
1292 | /* remote should refer to something like refs/remotes/ORIGIN/BRANCH */ | |
1293 | ||
1294 | if (git_reference_type(remote) != GIT_REF_SYMBOLIC || | |
1295 | git__prefixcmp(git_reference_target(remote), "refs/remotes/") != 0) | |
1296 | { | |
1297 | giterr_set(GITERR_SUBMODULE, | |
1298 | "Cannot resolve relative URL when HEAD is not symbolic"); | |
1299 | error = GIT_ENOTFOUND; | |
1300 | goto cleanup; | |
1301 | } | |
1302 | ||
1303 | scan = tgt = git_reference_target(remote) + strlen("refs/remotes/"); | |
1304 | while (*scan && (*scan != '/' || (scan > tgt && scan[-1] != '\\'))) | |
1305 | scan++; /* find non-escaped slash to end ORIGIN name */ | |
1306 | ||
1307 | error = git_buf_printf(&key, "remote.%.*s.url", (int)(scan - tgt), tgt); | |
1308 | if (error < 0) | |
1309 | goto cleanup; | |
1310 | ||
1311 | if ((error = git_config_get_string(&tgt, cfg, key.ptr)) < 0) | |
1312 | goto cleanup; | |
1313 | ||
1314 | error = git_buf_sets(url, tgt); | |
1315 | ||
1316 | cleanup: | |
1317 | git_buf_free(&key); | |
1318 | git_reference_free(head); | |
1319 | git_reference_free(remote); | |
1320 | ||
1321 | return error; | |
bfc9ca59 RB |
1322 | } |
1323 | ||
aa13bf05 RB |
1324 | static int submodule_update_config( |
1325 | git_submodule *submodule, | |
1326 | const char *attr, | |
1327 | const char *value, | |
1328 | bool overwrite, | |
1329 | bool only_existing) | |
bfc9ca59 | 1330 | { |
aa13bf05 RB |
1331 | int error; |
1332 | git_config *config; | |
1333 | git_buf key = GIT_BUF_INIT; | |
1334 | const char *old = NULL; | |
1335 | ||
1336 | assert(submodule); | |
1337 | ||
1338 | error = git_repository_config__weakptr(&config, submodule->owner); | |
1339 | if (error < 0) | |
1340 | return error; | |
1341 | ||
1342 | error = git_buf_printf(&key, "submodule.%s.%s", submodule->name, attr); | |
1343 | if (error < 0) | |
1344 | goto cleanup; | |
1345 | ||
1346 | if (git_config_get_string(&old, config, key.ptr) < 0) | |
1347 | giterr_clear(); | |
1348 | ||
1349 | if (!old && only_existing) | |
1350 | goto cleanup; | |
1351 | if (old && !overwrite) | |
1352 | goto cleanup; | |
1353 | if ((!old && !value) || (old && value && strcmp(old, value) == 0)) | |
1354 | goto cleanup; | |
1355 | ||
1356 | if (!value) | |
1357 | error = git_config_delete(config, key.ptr); | |
1358 | else | |
1359 | error = git_config_set_string(config, key.ptr, value); | |
1360 | ||
1361 | cleanup: | |
1362 | git_buf_free(&key); | |
1363 | return error; | |
bfc9ca59 RB |
1364 | } |
1365 | ||
5f4a61ae | 1366 | static int submodule_index_status(unsigned int *status, git_submodule *sm) |
aa13bf05 | 1367 | { |
5f4a61ae RB |
1368 | const git_oid *head_oid = git_submodule_head_oid(sm); |
1369 | const git_oid *index_oid = git_submodule_index_oid(sm); | |
aa13bf05 | 1370 | |
5f4a61ae RB |
1371 | if (!head_oid) { |
1372 | if (index_oid) | |
1373 | *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED; | |
1374 | } | |
1375 | else if (!index_oid) | |
1376 | *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED; | |
1377 | else if (!git_oid_equal(head_oid, index_oid)) | |
1378 | *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED; | |
aa13bf05 | 1379 | |
5f4a61ae | 1380 | return 0; |
aa13bf05 RB |
1381 | } |
1382 | ||
5f4a61ae | 1383 | static int submodule_wd_status(unsigned int *status, git_submodule *sm) |
bfc9ca59 | 1384 | { |
5f4a61ae RB |
1385 | int error = 0; |
1386 | const git_oid *wd_oid, *index_oid; | |
1387 | git_repository *sm_repo = NULL; | |
01fed0a8 | 1388 | |
5f4a61ae RB |
1389 | /* open repo now if we need it (so wd_oid() call won't reopen) */ |
1390 | if ((sm->ignore == GIT_SUBMODULE_IGNORE_NONE || | |
1391 | sm->ignore == GIT_SUBMODULE_IGNORE_UNTRACKED) && | |
1392 | (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0) | |
1393 | { | |
1394 | if ((error = git_submodule_open(&sm_repo, sm)) < 0) | |
1395 | return error; | |
aa13bf05 | 1396 | } |
bfc9ca59 | 1397 | |
5f4a61ae RB |
1398 | index_oid = git_submodule_index_oid(sm); |
1399 | wd_oid = git_submodule_wd_oid(sm); | |
aa13bf05 | 1400 | |
5f4a61ae RB |
1401 | if (!index_oid) { |
1402 | if (wd_oid) | |
1403 | *status |= GIT_SUBMODULE_STATUS_WD_ADDED; | |
aa13bf05 | 1404 | } |
5f4a61ae RB |
1405 | else if (!wd_oid) { |
1406 | if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 && | |
1407 | (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) | |
1408 | *status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED; | |
1409 | else | |
1410 | *status |= GIT_SUBMODULE_STATUS_WD_DELETED; | |
aa13bf05 | 1411 | } |
5f4a61ae RB |
1412 | else if (!git_oid_equal(index_oid, wd_oid)) |
1413 | *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED; | |
aa13bf05 | 1414 | |
5f4a61ae RB |
1415 | if (sm_repo != NULL) { |
1416 | git_tree *sm_head; | |
1417 | git_diff_options opt; | |
1418 | git_diff_list *diff; | |
aa13bf05 | 1419 | |
5f4a61ae RB |
1420 | /* the diffs below could be optimized with an early termination |
1421 | * option to the git_diff functions, but for now this is sufficient | |
1422 | * (and certainly no worse that what core git does). | |
1423 | */ | |
aa13bf05 | 1424 | |
5f4a61ae | 1425 | /* perform head-to-index diff on submodule */ |
aa13bf05 | 1426 | |
5f4a61ae RB |
1427 | if ((error = git_repository_head_tree(&sm_head, sm_repo)) < 0) |
1428 | return error; | |
bfc9ca59 | 1429 | |
5f4a61ae RB |
1430 | memset(&opt, 0, sizeof(opt)); |
1431 | if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE) | |
1432 | opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; | |
bfc9ca59 | 1433 | |
5f4a61ae | 1434 | error = git_diff_index_to_tree(sm_repo, &opt, sm_head, &diff); |
aa13bf05 | 1435 | |
5f4a61ae RB |
1436 | if (!error) { |
1437 | if (git_diff_entrycount(diff, -1) > 0) | |
1438 | *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; | |
aa13bf05 | 1439 | |
5f4a61ae RB |
1440 | git_diff_list_free(diff); |
1441 | diff = NULL; | |
1442 | } | |
aa13bf05 | 1443 | |
5f4a61ae | 1444 | git_tree_free(sm_head); |
bfc9ca59 | 1445 | |
5f4a61ae RB |
1446 | if (error < 0) |
1447 | return error; | |
aa13bf05 | 1448 | |
5f4a61ae | 1449 | /* perform index-to-workdir diff on submodule */ |
aa13bf05 | 1450 | |
5f4a61ae | 1451 | error = git_diff_workdir_to_index(sm_repo, &opt, &diff); |
aa13bf05 | 1452 | |
5f4a61ae RB |
1453 | if (!error) { |
1454 | int untracked = git_diff_entrycount(diff, GIT_DELTA_UNTRACKED); | |
bfc9ca59 | 1455 | |
5f4a61ae RB |
1456 | if (untracked > 0) |
1457 | *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; | |
bfc9ca59 | 1458 | |
5f4a61ae RB |
1459 | if (git_diff_entrycount(diff, -1) - untracked > 0) |
1460 | *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; | |
bfc9ca59 | 1461 | |
5f4a61ae RB |
1462 | git_diff_list_free(diff); |
1463 | diff = NULL; | |
aa13bf05 | 1464 | } |
aa13bf05 | 1465 | |
5f4a61ae RB |
1466 | git_repository_free(sm_repo); |
1467 | } | |
aa13bf05 RB |
1468 | |
1469 | return error; | |
bfc9ca59 | 1470 | } |