]> git.proxmox.com Git - libgit2.git/blob - src/submodule.c
Convert hashtable usage over to khash
[libgit2.git] / src / submodule.c
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"
15 #include "vector.h"
16 #include "posix.h"
17 #include "config_file.h"
18 #include "config.h"
19 #include "repository.h"
20
21 static git_cvar_map _sm_update_map[] = {
22 {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT},
23 {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE},
24 {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}
25 };
26
27 static git_cvar_map _sm_ignore_map[] = {
28 {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},
29 {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
30 {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED},
31 {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}
32 };
33
34 static inline khint_t str_hash_no_trailing_slash(const char *s)
35 {
36 khint_t h;
37
38 for (h = 0; *s; ++s)
39 if (s[1] || *s != '/')
40 h = (h << 5) - h + *s;
41
42 return h;
43 }
44
45 static inline int str_equal_no_trailing_slash(const char *a, const char *b)
46 {
47 size_t alen = a ? strlen(a) : 0;
48 size_t blen = b ? strlen(b) : 0;
49
50 if (alen && a[alen] == '/')
51 alen--;
52 if (blen && b[blen] == '/')
53 blen--;
54
55 return (alen == blen && strncmp(a, b, alen) == 0);
56 }
57
58 __KHASH_IMPL(str, static inline, const char *, void *, 1, str_hash_no_trailing_slash, str_equal_no_trailing_slash);
59
60 static git_submodule *submodule_alloc(const char *name)
61 {
62 git_submodule *sm = git__calloc(1, sizeof(git_submodule));
63 if (sm == NULL)
64 return sm;
65
66 sm->path = sm->name = git__strdup(name);
67 if (!sm->name) {
68 git__free(sm);
69 return NULL;
70 }
71
72 return sm;
73 }
74
75 static void submodule_release(git_submodule *sm, int decr)
76 {
77 if (!sm)
78 return;
79
80 sm->refcount -= decr;
81
82 if (sm->refcount == 0) {
83 if (sm->name != sm->path)
84 git__free(sm->path);
85 git__free(sm->name);
86 git__free(sm->url);
87 git__free(sm);
88 }
89 }
90
91 static int submodule_from_entry(
92 git_khash_str *smcfg, git_index_entry *entry)
93 {
94 git_submodule *sm;
95 void *old_sm;
96 khiter_t pos;
97 int error;
98
99 pos = git_khash_str_lookup_index(smcfg, entry->path);
100
101 if (git_khash_str_valid_index(smcfg, pos))
102 sm = git_khash_str_value_at(smcfg, pos);
103 else
104 sm = submodule_alloc(entry->path);
105
106 git_oid_cpy(&sm->oid, &entry->oid);
107
108 if (strcmp(sm->path, entry->path) != 0) {
109 if (sm->path != sm->name) {
110 git__free(sm->path);
111 sm->path = sm->name;
112 }
113 sm->path = git__strdup(entry->path);
114 if (!sm->path)
115 goto fail;
116 }
117
118 git_khash_str_insert2(smcfg, sm->path, sm, old_sm, error);
119 if (error < 0)
120 goto fail;
121 sm->refcount++;
122
123 if (old_sm && ((git_submodule *)old_sm) != sm) {
124 /* TODO: log warning about multiple entrys for same submodule path */
125 submodule_release(old_sm, 1);
126 }
127
128 return 0;
129
130 fail:
131 submodule_release(sm, 0);
132 return -1;
133 }
134
135 static int submodule_from_config(
136 const char *key, const char *value, void *data)
137 {
138 git_khash_str *smcfg = data;
139 const char *namestart;
140 const char *property;
141 git_buf name = GIT_BUF_INIT;
142 git_submodule *sm;
143 void *old_sm = NULL;
144 bool is_path;
145 khiter_t pos;
146 int error;
147
148 if (git__prefixcmp(key, "submodule.") != 0)
149 return 0;
150
151 namestart = key + strlen("submodule.");
152 property = strrchr(namestart, '.');
153 if (property == NULL)
154 return 0;
155 property++;
156 is_path = (strcmp(property, "path") == 0);
157
158 if (git_buf_set(&name, namestart, property - namestart - 1) < 0)
159 return -1;
160
161 pos = git_khash_str_lookup_index(smcfg, name.ptr);
162 if (!git_khash_str_valid_index(smcfg, pos) && is_path)
163 pos = git_khash_str_lookup_index(smcfg, value);
164 if (!git_khash_str_valid_index(smcfg, pos))
165 sm = submodule_alloc(name.ptr);
166 else
167 sm = git_khash_str_value_at(smcfg, pos);
168 if (!sm)
169 goto fail;
170
171 if (strcmp(sm->name, name.ptr) != 0) {
172 assert(sm->path == sm->name);
173 sm->name = git_buf_detach(&name);
174
175 git_khash_str_insert2(smcfg, sm->name, sm, old_sm, error);
176 if (error < 0)
177 goto fail;
178 sm->refcount++;
179 }
180 else if (is_path && strcmp(sm->path, value) != 0) {
181 assert(sm->path == sm->name);
182 sm->path = git__strdup(value);
183 if (sm->path == NULL)
184 goto fail;
185
186 git_khash_str_insert2(smcfg, sm->path, sm, old_sm, error);
187 if (error < 0)
188 goto fail;
189 sm->refcount++;
190 }
191 git_buf_free(&name);
192
193 if (old_sm && ((git_submodule *)old_sm) != sm) {
194 /* TODO: log warning about multiple submodules with same path */
195 submodule_release(old_sm, 1);
196 }
197
198 if (is_path)
199 return 0;
200
201 /* copy other properties into submodule entry */
202 if (strcmp(property, "url") == 0) {
203 if (sm->url) {
204 git__free(sm->url);
205 sm->url = NULL;
206 }
207 if ((sm->url = git__strdup(value)) == NULL)
208 goto fail;
209 }
210 else if (strcmp(property, "update") == 0) {
211 int val;
212 if (git_config_lookup_map_value(
213 _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) {
214 giterr_set(GITERR_INVALID,
215 "Invalid value for submodule update property: '%s'", value);
216 goto fail;
217 }
218 sm->update = (git_submodule_update_t)val;
219 }
220 else if (strcmp(property, "fetchRecurseSubmodules") == 0) {
221 if (git_config_parse_bool(&sm->fetch_recurse, value) < 0)
222 goto fail;
223 }
224 else if (strcmp(property, "ignore") == 0) {
225 int val;
226 if (git_config_lookup_map_value(
227 _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) {
228 giterr_set(GITERR_INVALID,
229 "Invalid value for submodule ignore property: '%s'", value);
230 goto fail;
231 }
232 sm->ignore = (git_submodule_ignore_t)val;
233 }
234 /* ignore other unknown submodule properties */
235
236 return 0;
237
238 fail:
239 submodule_release(sm, 0);
240 git_buf_free(&name);
241 return -1;
242 }
243
244 static int load_submodule_config(git_repository *repo)
245 {
246 int error;
247 git_index *index;
248 unsigned int i, max_i;
249 git_oid gitmodules_oid;
250 git_khash_str *smcfg;
251 struct git_config_file *mods = NULL;
252
253 if (repo->submodules)
254 return 0;
255
256 /* submodule data is kept in a hashtable with each submodule stored
257 * under both its name and its path. These are usually the same, but
258 * that is not guaranteed.
259 */
260 smcfg = git_khash_str_alloc();
261 GITERR_CHECK_ALLOC(smcfg);
262
263 /* scan index for gitmodules (and .gitmodules entry) */
264 if ((error = git_repository_index__weakptr(&index, repo)) < 0)
265 goto cleanup;
266 memset(&gitmodules_oid, 0, sizeof(gitmodules_oid));
267 max_i = git_index_entrycount(index);
268
269 for (i = 0; i < max_i; i++) {
270 git_index_entry *entry = git_index_get(index, i);
271 if (S_ISGITLINK(entry->mode)) {
272 if ((error = submodule_from_entry(smcfg, entry)) < 0)
273 goto cleanup;
274 }
275 else if (strcmp(entry->path, ".gitmodules") == 0)
276 git_oid_cpy(&gitmodules_oid, &entry->oid);
277 }
278
279 /* load .gitmodules from workdir if it exists */
280 if (git_repository_workdir(repo) != NULL) {
281 /* look in workdir for .gitmodules */
282 git_buf path = GIT_BUF_INIT;
283 if (!git_buf_joinpath(
284 &path, git_repository_workdir(repo), ".gitmodules") &&
285 git_path_isfile(path.ptr))
286 {
287 if (!(error = git_config_file__ondisk(&mods, path.ptr)))
288 error = git_config_file_open(mods);
289 }
290 git_buf_free(&path);
291 }
292
293 /* load .gitmodules from object cache if not in workdir */
294 if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) {
295 /* TODO: is it worth loading gitmodules from object cache? */
296 }
297
298 /* process .gitmodules info */
299 if (!error && mods != NULL)
300 error = git_config_file_foreach(mods, submodule_from_config, smcfg);
301
302 /* store submodule config in repo */
303 if (!error)
304 repo->submodules = smcfg;
305
306 cleanup:
307 if (mods != NULL)
308 git_config_file_free(mods);
309 if (error)
310 git_khash_str_free(smcfg);
311 return error;
312 }
313
314 void git_submodule_config_free(git_repository *repo)
315 {
316 git_khash_str *smcfg = repo->submodules;
317 git_submodule *sm;
318
319 repo->submodules = NULL;
320
321 if (smcfg == NULL)
322 return;
323
324 git_khash_str_foreach_value(smcfg, sm, {
325 submodule_release(sm,1);
326 });
327 git_khash_str_free(smcfg);
328 }
329
330 static int submodule_cmp(const void *a, const void *b)
331 {
332 return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
333 }
334
335 int git_submodule_foreach(
336 git_repository *repo,
337 int (*callback)(const char *name, void *payload),
338 void *payload)
339 {
340 int error;
341 git_submodule *sm;
342 git_vector seen = GIT_VECTOR_INIT;
343 seen._cmp = submodule_cmp;
344
345 if ((error = load_submodule_config(repo)) < 0)
346 return error;
347
348 git_khash_str_foreach_value(repo->submodules, sm, {
349 /* usually the following will not come into play */
350 if (sm->refcount > 1) {
351 if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND)
352 continue;
353 if ((error = git_vector_insert(&seen, sm)) < 0)
354 break;
355 }
356
357 if ((error = callback(sm->name, payload)) < 0)
358 break;
359 });
360
361 git_vector_free(&seen);
362
363 return error;
364 }
365
366 int git_submodule_lookup(
367 git_submodule **sm_ptr, /* NULL allowed if user only wants to test */
368 git_repository *repo,
369 const char *name) /* trailing slash is allowed */
370 {
371 khiter_t pos;
372
373 if (load_submodule_config(repo) < 0)
374 return -1;
375
376 pos = git_khash_str_lookup_index(repo->submodules, name);
377 if (!git_khash_str_valid_index(repo->submodules, pos))
378 return GIT_ENOTFOUND;
379
380 if (sm_ptr)
381 *sm_ptr = git_khash_str_value_at(repo->submodules, pos);
382
383 return 0;
384 }