Fix the help tests
[tig.git] / src / refdb.c
blobae1ff992ab706203b6ca448dfd0e0091eb30940b
1 /* Copyright (c) 2006-2015 Jonas Fonseca <jonas.fonseca@gmail.com>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #include "tig/tig.h"
15 #include "tig/map.h"
16 #include "tig/argv.h"
17 #include "tig/io.h"
18 #include "tig/watch.h"
19 #include "tig/options.h"
20 #include "tig/repo.h"
21 #include "tig/refdb.h"
23 static struct ref *refs_head = NULL;
24 static bool refs_tags;
26 DEFINE_STRING_MAP(refs_by_name, struct ref *, name, 32)
27 DEFINE_STRING_MAP(refs_by_id, struct ref *, id, 16)
29 int
30 ref_compare(const struct ref *ref1, const struct ref *ref2)
32 if (ref1->type != ref2->type)
33 return ref1->type - ref2->type;
34 return strcmp_numeric(ref1->name, ref2->name);
37 static int
38 ref_canonical_compare(const struct ref *ref1, const struct ref *ref2)
40 int tag_diff = !!ref_is_tag(ref2) - !!ref_is_tag(ref1);
42 if (tag_diff)
43 return tag_diff;
44 if (ref1->type != ref2->type)
45 return !tag_diff ? ref1->type - ref2->type : ref2->type - ref1->type;
46 return strcmp_numeric(ref1->name, ref2->name);
49 struct ref_visitor_data {
50 ref_visitor_fn visitor;
51 void *data;
54 static bool
55 foreach_ref_visitor(void *data, void *value)
57 struct ref_visitor_data *visitor_data = data;
58 const struct ref *ref = value;
60 if (!ref->valid)
61 return true;
62 return visitor_data->visitor(visitor_data->data, ref);
65 void
66 foreach_ref(ref_visitor_fn visitor, void *data)
68 struct ref_visitor_data visitor_data = { visitor, data };
70 string_map_foreach(&refs_by_name, foreach_ref_visitor, &visitor_data);
73 const struct ref *
74 get_ref_head()
76 return refs_head;
79 const struct ref *
80 get_ref_list(const char *id)
82 return string_map_get(&refs_by_id, id);
85 const struct ref *
86 get_canonical_ref(const char *id)
88 const struct ref *ref = NULL;
89 const struct ref *pos;
91 foreach_ref_list(pos, id)
92 if (!ref || ref_canonical_compare(pos, ref) < 0)
93 ref = pos;
95 return ref;
98 bool
99 ref_list_contains_tag(const char *id)
101 const struct ref *ref;
103 foreach_ref_list(ref, id)
104 if (ref_is_tag(ref))
105 return true;
107 return false;
110 struct ref_opt {
111 const char *remote;
112 const char *head;
113 enum watch_trigger changed;
116 static enum status_code
117 add_ref_to_id_map(struct ref *ref)
119 void **ref_lists_slot = string_map_put_to(&refs_by_id, ref->id);
121 if (!ref_lists_slot)
122 return SUCCESS;
124 /* First remove the ref from the ID list, to ensure that it is
125 * reinserted at the right position if the type changes. */
127 struct ref *list, *prev;
129 for (list = *ref_lists_slot, prev = NULL; list; prev = list, list = list->next)
130 if (list == ref) {
131 if (!prev)
132 *ref_lists_slot = ref->next;
133 else
134 prev->next = ref->next;
137 ref->next = NULL;
140 if (*ref_lists_slot == NULL || ref_compare(ref, *ref_lists_slot) <= 0) {
141 ref->next = *ref_lists_slot;
142 *ref_lists_slot = ref;
144 } else {
145 struct ref *list;
147 for (list = *ref_lists_slot; list->next; list = list->next) {
148 if (ref_compare(ref, list->next) <= 0)
149 break;
152 ref->next = list->next;
153 list->next = ref;
156 return SUCCESS;
159 static void
160 remove_ref_from_id_map(struct ref *ref)
162 void **ref_slot = string_map_get_at(&refs_by_id, ref->id);
163 struct ref *list = ref_slot ? *ref_slot : NULL;
164 struct ref *prev = NULL;
166 for (; list; prev = list, list = list->next) {
167 if (list != ref)
168 continue;
170 if (!prev)
171 *ref_slot = ref->next;
172 else
173 prev->next = ref->next;
174 ref->next = NULL;
175 break;
178 if (ref_slot && !*ref_slot)
179 string_map_remove(&refs_by_id, ref->id);
182 static enum status_code
183 add_to_refs(const char *id, size_t idlen, char *name, size_t namelen, struct ref_opt *opt)
185 struct ref *ref = NULL;
186 enum reference_type type = REFERENCE_BRANCH;
187 void **ref_slot = NULL;
189 if (!prefixcmp(name, "refs/tags/")) {
190 type = REFERENCE_TAG;
191 if (!suffixcmp(name, namelen, "^{}")) {
192 namelen -= 3;
193 name[namelen] = 0;
194 } else {
195 type = REFERENCE_LOCAL_TAG;
198 namelen -= STRING_SIZE("refs/tags/");
199 name += STRING_SIZE("refs/tags/");
201 } else if (!prefixcmp(name, "refs/remotes/")) {
202 type = REFERENCE_REMOTE;
203 namelen -= STRING_SIZE("refs/remotes/");
204 name += STRING_SIZE("refs/remotes/");
205 if (!strcmp(opt->remote, name))
206 type = REFERENCE_TRACKED_REMOTE;
208 } else if (!prefixcmp(name, "refs/replace/")) {
209 type = REFERENCE_REPLACE;
210 id = name + strlen("refs/replace/");
211 idlen = namelen - strlen("refs/replace/");
212 name = "replaced";
213 namelen = strlen(name);
215 } else if (!prefixcmp(name, "refs/heads/")) {
216 namelen -= STRING_SIZE("refs/heads/");
217 name += STRING_SIZE("refs/heads/");
218 if (strlen(opt->head) == namelen &&
219 !strncmp(opt->head, name, namelen))
220 type = REFERENCE_HEAD;
222 } else if (!strcmp(name, "HEAD")) {
223 /* Handle the case of HEAD not being a symbolic ref,
224 * i.e. during a rebase. */
225 if (*opt->head)
226 return SUCCESS;
227 type = REFERENCE_HEAD;
230 /* If we are reloading or it's an annotated tag, replace the
231 * previous SHA1 with the resolved commit id; relies on the fact
232 * git-ls-remote lists the commit id of an annotated tag right
233 * before the commit id it points to. */
234 if (type == REFERENCE_REPLACE) {
235 ref = string_map_remove(&refs_by_id, id);
237 } else {
238 ref_slot = string_map_put_to(&refs_by_name, name);
239 if (!ref_slot)
240 return ERROR_OUT_OF_MEMORY;
241 ref = *ref_slot;
244 if (!ref) {
245 ref = calloc(1, sizeof(*ref) + namelen);
246 if (!ref)
247 return ERROR_OUT_OF_MEMORY;
248 strncpy(ref->name, name, namelen);
249 if (ref_slot)
250 *ref_slot = ref;
253 if (strncmp(ref->id, id, idlen) || ref->type != type) {
254 opt->changed |= WATCH_REFS;
255 if (*ref->id)
256 remove_ref_from_id_map(ref);
259 ref->valid = true;
260 ref->type = type;
261 string_ncopy_do(ref->id, SIZEOF_REV, id, idlen);
263 if (type == REFERENCE_HEAD) {
264 if (!refs_head ||
265 (refs_head != ref && memcmp(refs_head, ref, sizeof(*ref))))
266 opt->changed |= WATCH_HEAD;
267 refs_head = ref;
270 if (type == REFERENCE_TAG)
271 refs_tags++;
273 return add_ref_to_id_map(ref);
276 static enum status_code
277 read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
279 return add_to_refs(id, idlen, name, namelen, data);
282 static bool
283 invalidate_refs(void *data, void *ref_)
285 struct ref *ref = ref_;
287 ref->valid = 0;
288 ref->next = NULL;
289 return true;
292 static bool
293 cleanup_refs(void *data, void *ref_)
295 struct ref_opt *opt = data;
296 struct ref *ref = ref_;
298 if (!ref->valid) {
299 ref->id[0] = 0;
300 opt->changed |= WATCH_REFS;
303 return true;
306 static enum status_code
307 reload_refs(bool force)
309 const char *ls_remote_argv[SIZEOF_ARG] = {
310 "git", "show-ref", "--head", "--dereference", NULL
312 static bool init = false;
313 struct ref_opt opt = { repo.remote, repo.head, WATCH_NONE };
314 struct repo_info old_repo = repo;
315 enum status_code code;
317 if (!init) {
318 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
319 return ERROR_OUT_OF_MEMORY;
320 init = true;
323 if (!*repo.git_dir)
324 return SUCCESS;
326 if (force || !*repo.head)
327 load_repo_head();
329 if (strcmp(old_repo.head, repo.head))
330 opt.changed |= WATCH_HEAD;
332 refs_head = NULL;
333 refs_tags = 0;
334 string_map_clear(&refs_by_id);
335 string_map_foreach(&refs_by_name, invalidate_refs, NULL);
337 code = io_run_load(ls_remote_argv, " ", read_ref, &opt);
338 if (code != SUCCESS)
339 return code;
341 string_map_foreach(&refs_by_name, cleanup_refs, &opt);
343 if (opt.changed)
344 watch_apply(NULL, opt.changed);
346 return SUCCESS;
349 enum status_code
350 load_refs(bool force)
352 static bool loaded = false;
354 if (!force && loaded)
355 return SUCCESS;
357 loaded = true;
358 return reload_refs(force);
361 enum status_code
362 add_ref(const char *id, char *name, const char *remote_name, const char *head)
364 struct ref_opt opt = { remote_name, head };
366 return add_to_refs(id, strlen(id), name, strlen(name), &opt);
369 void
370 ref_update_env(struct argv_env *env, const struct ref *ref, bool recurse)
372 bool clear = recurse ? !ref->next : true;
374 if (recurse && ref->next)
375 ref_update_env(env, ref->next, true);
377 if (clear)
378 env->tag[0] = env->remote[0] = env->branch[0] = 0;
380 string_copy_rev(env->commit, ref->id);
382 if (ref_is_tag(ref)) {
383 string_ncopy(env->tag, ref->name, strlen(ref->name));
385 } else if (ref_is_remote(ref)) {
386 const char *sep = strchr(ref->name, '/');
388 if (!sep)
389 return;
390 string_ncopy(env->remote, ref->name, sep - ref->name);
391 string_ncopy(env->branch, sep + 1, strlen(sep + 1));
393 } else if (ref->type == REFERENCE_BRANCH || ref->type == REFERENCE_HEAD) {
394 string_ncopy(env->branch, ref->name, strlen(ref->name));
398 bool
399 refs_contain_tag(void)
401 return refs_tags > 0;
404 const struct ref_format *
405 get_ref_format(struct ref_format **ref_formats, const struct ref *ref)
407 static const struct ref_format default_format = { "", "" };
409 if (ref_formats) {
410 struct ref_format *format = ref_formats[ref->type];
412 if (!format && ref_is_tag(ref))
413 format = ref_formats[REFERENCE_TAG];
414 if (!format && ref_is_remote(ref))
415 format = ref_formats[REFERENCE_REMOTE];
416 if (!format)
417 format = ref_formats[REFERENCE_BRANCH];
418 if (format)
419 return format;
422 return &default_format;
425 static enum status_code
426 parse_ref_format_arg(struct ref_format **ref_formats, const char *arg, const struct enum_map *map)
428 size_t arglen = strlen(arg);
429 const char *pos;
431 for (pos = arg; *pos && arglen > 0; pos++, arglen--) {
432 enum reference_type type;
434 for (type = 0; type < map->size; type++) {
435 const struct enum_map_entry *entry = &map->entries[type];
436 struct ref_format *format;
438 if (arglen < entry->namelen ||
439 string_enum_compare(pos, entry->name, entry->namelen))
440 continue;
442 format = malloc(sizeof(*format));
443 if (!format)
444 return ERROR_OUT_OF_MEMORY;
445 format->start = strndup(arg, pos - arg);
446 format->end = strdup(pos + entry->namelen);
447 if (!format->start || !format->end) {
448 free((void *) format->start);
449 free((void *) format->end);
450 free(format);
451 return ERROR_OUT_OF_MEMORY;
454 ref_formats[type] = format;
455 return SUCCESS;
459 return error("Unknown ref format: %s", arg);
462 enum status_code
463 parse_ref_formats(struct ref_format ***formats, const char *argv[])
465 const struct enum_map *map = reference_type_map;
466 int argc;
468 if (!*formats) {
469 *formats = calloc(reference_type_map->size, sizeof(struct ref_format *));
470 if (!*formats)
471 return ERROR_OUT_OF_MEMORY;
474 for (argc = 0; argv[argc]; argc++) {
475 enum status_code code = parse_ref_format_arg(*formats, argv[argc], map);
476 if (code != SUCCESS)
477 return code;
480 return SUCCESS;
483 enum status_code
484 format_ref_formats(struct ref_format **formats, char buf[], size_t bufsize)
486 const struct enum_map *map = reference_type_map;
487 char name[SIZEOF_STR];
488 enum reference_type type;
489 size_t bufpos = 0;
490 const char *sep = "";
492 for (type = 0; type < map->size; type++) {
493 struct ref_format *format = formats[type];
495 if (!format)
496 continue;
498 if (!enum_name_copy(name, sizeof(name), map->entries[type].name)
499 || !string_nformat(buf, bufsize, &bufpos, "%s%s%s%s",
500 sep, format->start, name, format->end))
501 return error("No space left in buffer");
503 sep = " ";
506 return SUCCESS;
509 /* vim: set ts=8 sw=8 noexpandtab: */