tig-2.1.1
[tig.git] / src / refdb.c
blobf50d44956a467bd0c6bb5f0c469be227ab2c26c4
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 int
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 OK;
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 OK;
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 int
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 OK;
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 ERR;
241 ref = *ref_slot;
244 if (!ref) {
245 ref = calloc(1, sizeof(*ref) + namelen);
246 if (!ref)
247 return ERR;
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 int
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 int
307 reload_refs(bool force)
309 const char *ls_remote_argv[SIZEOF_ARG] = {
310 "git", "ls-remote", repo.git_dir, NULL
312 static bool init = FALSE;
313 struct ref_opt opt = { repo.remote, repo.head, WATCH_NONE };
314 struct repo_info old_repo = repo;
316 if (!init) {
317 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
318 return ERR;
319 init = TRUE;
322 if (!*repo.git_dir)
323 return OK;
325 if (force || !*repo.head)
326 load_repo_head();
328 if (strcmp(old_repo.head, repo.head))
329 opt.changed |= WATCH_HEAD;
331 refs_head = NULL;
332 refs_tags = 0;
333 string_map_clear(&refs_by_id);
334 string_map_foreach(&refs_by_name, invalidate_refs, NULL);
336 if (io_run_load(ls_remote_argv, "\t", read_ref, &opt) == ERR)
337 return ERR;
339 string_map_foreach(&refs_by_name, cleanup_refs, &opt);
341 if (opt.changed)
342 watch_apply(NULL, opt.changed);
344 return OK;
348 load_refs(bool force)
350 static bool loaded = FALSE;
352 if (!force && loaded)
353 return OK;
355 loaded = TRUE;
356 return reload_refs(force);
360 add_ref(const char *id, char *name, const char *remote_name, const char *head)
362 struct ref_opt opt = { remote_name, head };
364 return add_to_refs(id, strlen(id), name, strlen(name), &opt);
367 void
368 ref_update_env(struct argv_env *env, const struct ref *ref, bool clear)
370 if (clear)
371 env->tag[0] = env->remote[0] = env->branch[0] = 0;
373 string_copy_rev(env->commit, ref->id);
375 if (ref_is_tag(ref)) {
376 string_ncopy(env->tag, ref->name, strlen(ref->name));
378 } else if (ref_is_remote(ref)) {
379 const char *sep = strchr(ref->name, '/');
381 if (!sep)
382 return;
383 string_ncopy(env->remote, ref->name, sep - ref->name);
384 string_ncopy(env->branch, sep + 1, strlen(sep + 1));
386 } else if (ref->type == REFERENCE_BRANCH) {
387 string_ncopy(env->branch, ref->name, strlen(ref->name));
391 bool
392 refs_contain_tag(void)
394 return refs_tags > 0;
397 const struct ref_format *
398 get_ref_format(struct ref_format **ref_formats, const struct ref *ref)
400 static const struct ref_format default_format = { "", "" };
402 if (ref_formats) {
403 struct ref_format *format = ref_formats[ref->type];
405 if (!format && ref_is_tag(ref))
406 format = ref_formats[REFERENCE_TAG];
407 if (!format && ref_is_remote(ref))
408 format = ref_formats[REFERENCE_REMOTE];
409 if (!format)
410 format = ref_formats[REFERENCE_BRANCH];
411 if (format)
412 return format;
415 return &default_format;
418 static enum status_code
419 parse_ref_format_arg(struct ref_format **ref_formats, const char *arg, const struct enum_map *map)
421 size_t arglen = strlen(arg);
422 const char *pos;
424 for (pos = arg; *pos && arglen > 0; pos++, arglen--) {
425 enum reference_type type;
427 for (type = 0; type < map->size; type++) {
428 const struct enum_map_entry *entry = &map->entries[type];
429 struct ref_format *format;
431 if (arglen < entry->namelen ||
432 string_enum_compare(pos, entry->name, entry->namelen))
433 continue;
435 format = malloc(sizeof(*format));
436 if (!format)
437 return ERROR_OUT_OF_MEMORY;
438 format->start = strndup(arg, pos - arg);
439 format->end = strdup(pos + entry->namelen);
440 if (!format->start || !format->end) {
441 free((void *) format->start);
442 free((void *) format->end);
443 free(format);
444 return ERROR_OUT_OF_MEMORY;
447 ref_formats[type] = format;
448 return SUCCESS;
452 return error("Unknown ref format: %s", arg);
455 enum status_code
456 parse_ref_formats(struct ref_format ***formats, const char *argv[])
458 const struct enum_map *map = reference_type_map;
459 int argc;
461 if (!*formats) {
462 *formats = calloc(reference_type_map->size, sizeof(struct ref_format *));
463 if (!*formats)
464 return ERROR_OUT_OF_MEMORY;
467 for (argc = 0; argv[argc]; argc++) {
468 enum status_code code = parse_ref_format_arg(*formats, argv[argc], map);
469 if (code != SUCCESS)
470 return code;
473 return SUCCESS;
476 enum status_code
477 format_ref_formats(struct ref_format **formats, char buf[], size_t bufsize)
479 const struct enum_map *map = reference_type_map;
480 char name[SIZEOF_STR];
481 enum reference_type type;
482 size_t bufpos = 0;
483 const char *sep = "";
485 for (type = 0; type < map->size; type++) {
486 struct ref_format *format = formats[type];
488 if (!format)
489 continue;
491 if (!enum_name_copy(name, sizeof(name), map->entries[type].name)
492 || !string_nformat(buf, bufsize, &bufpos, "%s%s%s%s",
493 sep, format->start, name, format->end))
494 return error("No space left in buffer");
496 sep = " ";
499 return SUCCESS;
502 /* vim: set ts=8 sw=8 noexpandtab: */