Fix segfault when refreshing the status view
[tig.git] / src / refdb.c
blobd91bcc2256a19fd50483d6fdeb8453690c0e8da8
1 /* Copyright (c) 2006-2014 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 void
50 foreach_ref(bool (*visitor)(void *data, const struct ref *ref), void *data)
52 string_map_foreach(&refs_by_name, (string_map_iterator_fn) visitor, data);
55 const struct ref *
56 get_ref_head()
58 return refs_head;
61 const struct ref *
62 get_ref_list(const char *id)
64 return string_map_get(&refs_by_id, id);
67 const struct ref *
68 get_canonical_ref(const char *id)
70 const struct ref *ref = NULL;
71 const struct ref *pos;
73 foreach_ref_list(pos, id)
74 if (!ref || ref_canonical_compare(pos, ref) < 0)
75 ref = pos;
77 return ref;
80 bool
81 ref_list_contains_tag(const char *id)
83 const struct ref *ref;
85 foreach_ref_list(ref, id)
86 if (ref_is_tag(ref))
87 return TRUE;
89 return FALSE;
92 struct ref_opt {
93 const char *remote;
94 const char *head;
95 enum watch_trigger changed;
98 static int
99 add_to_refs(const char *id, size_t idlen, char *name, size_t namelen, struct ref_opt *opt)
101 struct ref *ref = NULL;
102 enum reference_type type = REFERENCE_BRANCH;
103 void **ref_lists_slot;
104 void **ref_slot;
106 if (!prefixcmp(name, "refs/tags/")) {
107 type = REFERENCE_TAG;
108 if (!suffixcmp(name, namelen, "^{}")) {
109 namelen -= 3;
110 name[namelen] = 0;
111 } else {
112 type = REFERENCE_LOCAL_TAG;
115 namelen -= STRING_SIZE("refs/tags/");
116 name += STRING_SIZE("refs/tags/");
118 } else if (!prefixcmp(name, "refs/remotes/")) {
119 type = REFERENCE_REMOTE;
120 namelen -= STRING_SIZE("refs/remotes/");
121 name += STRING_SIZE("refs/remotes/");
122 if (!strcmp(opt->remote, name))
123 type = REFERENCE_TRACKED_REMOTE;
125 } else if (!prefixcmp(name, "refs/replace/")) {
126 type = REFERENCE_REPLACE;
127 id = name + strlen("refs/replace/");
128 idlen = namelen - strlen("refs/replace/");
129 name = "replaced";
130 namelen = strlen(name);
132 } else if (!prefixcmp(name, "refs/heads/")) {
133 namelen -= STRING_SIZE("refs/heads/");
134 name += STRING_SIZE("refs/heads/");
135 if (strlen(opt->head) == namelen &&
136 !strncmp(opt->head, name, namelen))
137 type = REFERENCE_HEAD;
139 } else if (!strcmp(name, "HEAD")) {
140 /* Handle the case of HEAD not being a symbolic ref,
141 * i.e. during a rebase. */
142 if (*opt->head)
143 return OK;
144 type = REFERENCE_HEAD;
147 /* If we are reloading or it's an annotated tag, replace the
148 * previous SHA1 with the resolved commit id; relies on the fact
149 * git-ls-remote lists the commit id of an annotated tag right
150 * before the commit id it points to. */
151 if (type == REFERENCE_REPLACE) {
152 ref_slot = string_map_put_to(&refs_by_id, id);
153 if (!ref_slot)
154 return ERR;
155 if (*ref_slot)
156 ref = string_map_remove(&refs_by_id, ref_slot);
158 } else {
159 ref_slot = string_map_put_to(&refs_by_name, name);
160 if (!ref_slot)
161 return ERR;
162 ref = *ref_slot;
165 if (!ref) {
166 ref = calloc(1, sizeof(*ref) + namelen);
167 if (!ref)
168 return ERR;
169 strncpy(ref->name, name, namelen);
170 *ref_slot = ref;
173 if (strncmp(ref->id, id, idlen) || ref->type != type)
174 opt->changed |= WATCH_REFS;
176 ref->valid = TRUE;
177 ref->type = type;
178 string_ncopy_do(ref->id, SIZEOF_REV, id, idlen);
180 if (type == REFERENCE_HEAD) {
181 if (!refs_head ||
182 (refs_head != ref && memcmp(refs_head, ref, sizeof(*ref))))
183 opt->changed |= WATCH_HEAD;
184 refs_head = ref;
187 if (type == REFERENCE_TAG)
188 refs_tags++;
190 ref_lists_slot = string_map_put_to(&refs_by_id, id);
191 if (!ref_lists_slot)
192 return OK;
194 /* First remove the ref from the ID list, to ensure that it is
195 * reinserted at the right position if the type changes. */
197 struct ref *list, *prev;
199 for (list = *ref_lists_slot, prev = NULL; list; prev = list, list = list->next)
200 if (list == ref) {
201 if (!prev)
202 *ref_lists_slot = ref->next;
203 else
204 prev->next = ref->next;
207 ref->next = NULL;
210 ref->next = *ref_lists_slot;
211 *ref_lists_slot = ref;
213 while (ref->next) {
214 struct ref *head = ref->next;
216 if (head == ref || ref_compare(ref, head) <= 0)
217 break;
219 if (*ref_lists_slot == ref)
220 *ref_lists_slot = head;
221 ref->next = head->next;
222 head->next = ref;
225 return OK;
228 static int
229 read_ref(char *id, size_t idlen, char *name, size_t namelen, void *data)
231 return add_to_refs(id, idlen, name, namelen, data);
234 static bool
235 invalidate_refs(void *data, void *ref_)
237 struct ref *ref = ref_;
239 ref->valid = 0;
240 ref->next = NULL;
241 return TRUE;
244 static bool
245 cleanup_refs(void *data, void *ref_)
247 struct ref_opt *opt = data;
248 struct ref *ref = ref_;
250 if (!ref->valid) {
251 ref->id[0] = 0;
252 opt->changed |= WATCH_REFS;
255 return TRUE;
258 static int
259 reload_refs(bool force)
261 const char *ls_remote_argv[SIZEOF_ARG] = {
262 "git", "ls-remote", repo.git_dir, NULL
264 static bool init = FALSE;
265 struct ref_opt opt = { repo.remote, repo.head, WATCH_NONE };
266 struct repo_info old_repo = repo;
268 if (!init) {
269 if (!argv_from_env(ls_remote_argv, "TIG_LS_REMOTE"))
270 return ERR;
271 init = TRUE;
274 if (!*repo.git_dir)
275 return OK;
277 if (force || !*repo.head)
278 load_repo_head();
280 if (strcmp(old_repo.head, repo.head))
281 opt.changed |= WATCH_HEAD;
283 refs_head = NULL;
284 refs_tags = 0;
285 string_map_clear(&refs_by_id);
286 string_map_foreach(&refs_by_name, invalidate_refs, NULL);
288 if (io_run_load(ls_remote_argv, "\t", read_ref, &opt) == ERR)
289 return ERR;
291 string_map_foreach(&refs_by_name, cleanup_refs, &opt);
293 if (opt.changed)
294 watch_apply(NULL, opt.changed);
296 return OK;
300 load_refs(bool force)
302 static bool loaded = FALSE;
304 if (!force && loaded)
305 return OK;
307 loaded = TRUE;
308 return reload_refs(force);
312 add_ref(const char *id, char *name, const char *remote_name, const char *head)
314 struct ref_opt opt = { remote_name, head };
316 return add_to_refs(id, strlen(id), name, strlen(name), &opt);
319 void
320 ref_update_env(struct argv_env *env, const struct ref *ref, bool clear)
322 if (clear)
323 env->tag[0] = env->remote[0] = env->branch[0] = 0;
325 string_copy_rev(env->commit, ref->id);
327 if (ref_is_tag(ref)) {
328 string_ncopy(env->tag, ref->name, strlen(ref->name));
330 } else if (ref_is_remote(ref)) {
331 const char *sep = strchr(ref->name, '/');
333 if (!sep)
334 return;
335 string_ncopy(env->remote, ref->name, sep - ref->name);
336 string_ncopy(env->branch, sep + 1, strlen(sep + 1));
338 } else if (ref->type == REFERENCE_BRANCH) {
339 string_ncopy(env->branch, ref->name, strlen(ref->name));
343 bool
344 refs_contain_tag(void)
346 return refs_tags > 0;
349 const struct ref_format *
350 get_ref_format(struct ref_format **ref_formats, const struct ref *ref)
352 static const struct ref_format default_format = { "", "" };
354 if (ref_formats) {
355 struct ref_format *format = ref_formats[ref->type];
357 if (!format && ref_is_tag(ref))
358 format = ref_formats[REFERENCE_TAG];
359 if (!format && ref_is_remote(ref))
360 format = ref_formats[REFERENCE_REMOTE];
361 if (!format)
362 format = ref_formats[REFERENCE_BRANCH];
363 if (format)
364 return format;
367 return &default_format;
370 static enum status_code
371 parse_ref_format_arg(struct ref_format **ref_formats, const char *arg, const struct enum_map *map)
373 size_t arglen = strlen(arg);
374 const char *pos;
376 for (pos = arg; *pos && arglen > 0; pos++, arglen--) {
377 enum reference_type type;
379 for (type = 0; type < map->size; type++) {
380 const struct enum_map_entry *entry = &map->entries[type];
381 struct ref_format *format;
383 if (arglen < entry->namelen ||
384 string_enum_compare(pos, entry->name, entry->namelen))
385 continue;
387 format = malloc(sizeof(*format));
388 if (!format)
389 return ERROR_OUT_OF_MEMORY;
390 format->start = strndup(arg, pos - arg);
391 format->end = strdup(pos + entry->namelen);
392 if (!format->start || !format->end) {
393 free((void *) format->start);
394 free((void *) format->end);
395 free(format);
396 return ERROR_OUT_OF_MEMORY;
399 ref_formats[type] = format;
400 return SUCCESS;
404 return error("Unknown ref format: %s", arg);
407 enum status_code
408 parse_ref_formats(struct ref_format ***formats, const char *argv[])
410 const struct enum_map *map = reference_type_map;
411 int argc;
413 if (!*formats) {
414 *formats = calloc(reference_type_map->size, sizeof(struct ref_format *));
415 if (!*formats)
416 return ERROR_OUT_OF_MEMORY;
419 for (argc = 0; argv[argc]; argc++) {
420 enum status_code code = parse_ref_format_arg(*formats, argv[argc], map);
421 if (code != SUCCESS)
422 return code;
425 return SUCCESS;
428 enum status_code
429 format_ref_formats(struct ref_format **formats, char buf[], size_t bufsize)
431 const struct enum_map *map = reference_type_map;
432 char name[SIZEOF_STR];
433 enum reference_type type;
434 size_t bufpos = 0;
435 const char *sep = "";
437 for (type = 0; type < map->size; type++) {
438 struct ref_format *format = formats[type];
440 if (!format)
441 continue;
443 if (!enum_name_copy(name, sizeof(name), map->entries[type].name)
444 || !string_nformat(buf, bufsize, &bufpos, "%s%s%s%s",
445 sep, format->start, name, format->end))
446 return error("No space left in buffer");
448 sep = " ";
451 return SUCCESS;
454 /* vim: set ts=8 sw=8 noexpandtab: */