Fix insertion/ordering of refs in refs_by_id map.
[tig.git] / src / main.c
blobb429f2582a8ff77cf996fea87c3a81c7ec97c844
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/repo.h"
15 #include "tig/options.h"
16 #include "tig/parse.h"
17 #include "tig/watch.h"
18 #include "tig/graph.h"
19 #include "tig/display.h"
20 #include "tig/view.h"
21 #include "tig/draw.h"
22 #include "tig/git.h"
23 #include "tig/status.h"
24 #include "tig/stage.h"
25 #include "tig/main.h"
26 #include "tig/diff.h"
29 * Main view backend
32 DEFINE_ALLOCATOR(realloc_reflogs, char *, 32)
34 static bool main_add_changes(struct view *view, struct main_state *state, const char *parent);
36 static void
37 main_register_commit(struct view *view, struct commit *commit, const char *ids, bool is_boundary)
39 struct main_state *state = view->private;
40 struct graph *graph = state->graph;
42 string_copy_rev(commit->id, ids);
44 /* FIXME: lazily check index state here instead of in main_open. */
45 if ((state->add_changes_unstaged || state->add_changes_staged) && is_head_commit(commit->id)) {
46 main_add_changes(view, state, ids);
47 state->add_changes_unstaged = state->add_changes_staged = FALSE;
50 if (state->with_graph)
51 graph->add_commit(graph, &commit->graph, commit->id, ids, is_boundary);
54 static struct commit *
55 main_add_commit(struct view *view, enum line_type type, struct commit *template,
56 const char *title, bool custom)
58 struct main_state *state = view->private;
59 size_t titlelen;
60 struct commit *commit;
61 char buf[SIZEOF_STR / 2];
62 struct line *line;
64 /* FIXME: More graceful handling of titles; append "..." to
65 * shortened titles, etc. */
66 string_expand(buf, sizeof(buf), title, 1);
67 title = buf;
68 titlelen = strlen(title);
70 line = add_line_alloc(view, &commit, type, titlelen, custom);
71 if (!line)
72 return NULL;
74 *commit = *template;
75 strncpy(commit->title, title, titlelen);
76 memset(template, 0, sizeof(*template));
77 state->reflogmsg[0] = 0;
79 view_column_info_update(view, line);
80 return commit;
83 static inline void
84 main_flush_commit(struct view *view, struct commit *commit)
86 if (*commit->id)
87 main_add_commit(view, LINE_MAIN_COMMIT, commit, "", FALSE);
90 static bool
91 main_add_changes_commit(struct view *view, enum line_type type, const char *parent, const char *title)
93 char ids[SIZEOF_STR] = NULL_ID " ";
94 struct main_state *state = view->private;
95 struct graph *graph = state->graph;
96 struct commit commit = {{0}};
97 struct timeval now;
98 struct timezone tz;
100 if (!parent)
101 return TRUE;
103 if (*parent)
104 string_copy_rev(ids + STRING_SIZE(NULL_ID " "), parent);
105 else
106 ids[STRING_SIZE(NULL_ID)] = 0;
108 if (!gettimeofday(&now, &tz)) {
109 commit.time.tz = tz.tz_minuteswest * 60;
110 commit.time.sec = now.tv_sec - commit.time.tz;
113 commit.author = &unknown_ident;
114 main_register_commit(view, &commit, ids, FALSE);
115 if (state->with_graph && *parent)
116 graph->render_parents(graph, &commit.graph);
118 if (!main_add_commit(view, type, &commit, title, TRUE))
119 return FALSE;
121 return TRUE;
124 static bool
125 main_check_index(struct view *view, struct main_state *state)
127 struct index_diff diff;
129 if (!index_diff(&diff, FALSE, FALSE))
130 return FALSE;
132 if (!diff.unstaged) {
133 watch_apply(&view->watch, WATCH_INDEX_UNSTAGED_NO);
134 } else {
135 watch_apply(&view->watch, WATCH_INDEX_UNSTAGED_YES);
136 state->add_changes_unstaged = TRUE;
139 if (!diff.staged) {
140 watch_apply(&view->watch, WATCH_INDEX_STAGED_NO);
141 } else {
142 watch_apply(&view->watch, WATCH_INDEX_STAGED_YES);
143 state->add_changes_staged = TRUE;
146 return TRUE;
149 static bool
150 main_add_changes(struct view *view, struct main_state *state, const char *parent)
152 const char *staged_parent = parent;
153 const char *unstaged_parent = NULL_ID;
155 if (!state->add_changes_staged) {
156 staged_parent = NULL;
157 unstaged_parent = parent;
160 if (!state->add_changes_unstaged) {
161 unstaged_parent = NULL;
164 return main_add_changes_commit(view, LINE_STAT_UNSTAGED, unstaged_parent, "Unstaged changes")
165 && main_add_changes_commit(view, LINE_STAT_STAGED, staged_parent, "Staged changes");
168 static bool
169 main_check_argv(struct view *view, const char *argv[])
171 struct main_state *state = view->private;
172 bool with_reflog = FALSE;
173 int i;
175 for (i = 0; argv[i]; i++) {
176 const char *arg = argv[i];
177 struct rev_flags rev_flags = {0};
179 if (!strcmp(arg, "--graph")) {
180 struct view_column *column = get_view_column(view, VIEW_COLUMN_COMMIT_TITLE);
182 if (column) {
183 column->opt.commit_title.graph = TRUE;
184 if (opt_commit_order != COMMIT_ORDER_REVERSE)
185 state->with_graph = TRUE;
187 argv[i] = "";
188 continue;
191 if (!strcmp(arg, "--first-parent"))
192 state->first_parent = TRUE;
194 if (!argv_parse_rev_flag(arg, &rev_flags))
195 continue;
197 if (rev_flags.with_reflog)
198 with_reflog = TRUE;
199 if (!rev_flags.with_graph)
200 state->with_graph = FALSE;
201 arg += rev_flags.search_offset;
202 if (*arg && !*view->env->search)
203 string_ncopy(view->env->search, arg, strlen(arg));
206 return with_reflog;
209 static enum graph_display
210 main_with_graph(struct view *view, struct view_column *column, enum open_flags flags)
212 return column && opt_commit_order != COMMIT_ORDER_REVERSE && !open_in_pager_mode(flags)
213 ? column->opt.commit_title.graph : GRAPH_DISPLAY_NO;
216 static bool
217 main_open(struct view *view, enum open_flags flags)
219 struct view_column *commit_title_column = get_view_column(view, VIEW_COLUMN_COMMIT_TITLE);
220 enum graph_display graph_display = main_with_graph(view, commit_title_column, flags);
221 const char *pretty_custom_argv[] = {
222 GIT_MAIN_LOG_CUSTOM(encoding_arg, commit_order_arg_with_graph(graph_display),
223 "%(mainargs)", "%(cmdlineargs)", "%(revargs)", "%(fileargs)")
225 const char *pretty_raw_argv[] = {
226 GIT_MAIN_LOG_RAW(encoding_arg, commit_order_arg_with_graph(graph_display),
227 "%(mainargs)", "%(cmdlineargs)", "%(revargs)", "%(fileargs)")
229 struct main_state *state = view->private;
230 const char **main_argv = pretty_custom_argv;
231 enum watch_trigger changes_triggers = WATCH_NONE;
233 if (opt_show_changes && repo.is_inside_work_tree)
234 changes_triggers |= WATCH_INDEX;
236 state->with_graph = graph_display != GRAPH_DISPLAY_NO;
238 if (opt_rev_args && main_check_argv(view, opt_rev_args))
239 main_argv = pretty_raw_argv;
241 if (state->with_graph) {
242 state->graph = init_graph(commit_title_column->opt.commit_title.graph);
243 if (!state->graph)
244 return FALSE;
247 if (open_in_pager_mode(flags)) {
248 changes_triggers = WATCH_NONE;
251 /* This calls reset_view() so must be before adding changes commits. */
252 if (!begin_update(view, NULL, main_argv, flags))
253 return FALSE;
255 /* Register watch before changes commits are added to record the
256 * start. */
257 if (view_can_refresh(view))
258 watch_register(&view->watch, WATCH_HEAD | WATCH_REFS | changes_triggers);
260 if (changes_triggers)
261 main_check_index(view, state);
263 return TRUE;
266 void
267 main_done(struct view *view)
269 struct main_state *state = view->private;
270 int i;
272 for (i = 0; i < view->lines; i++) {
273 struct commit *commit = view->line[i].data;
275 free(commit->graph.symbols);
278 if (state->graph)
279 state->graph->done(state->graph);
281 for (i = 0; i < state->reflogs; i++)
282 free(state->reflog[i]);
283 free(state->reflog);
286 #define main_check_commit_refs(line) !((line)->no_commit_refs)
287 #define main_mark_no_commit_refs(line) (((struct line *) (line))->no_commit_refs = 1)
289 static inline const struct ref *
290 main_get_commit_refs(const struct line *line, struct commit *commit)
292 const struct ref *refs = NULL;
294 if (main_check_commit_refs(line) && !(refs = get_ref_list(commit->id)))
295 main_mark_no_commit_refs(line);
297 return refs;
300 bool
301 main_get_column_data(struct view *view, const struct line *line, struct view_column_data *column_data)
303 struct main_state *state = view->private;
304 struct commit *commit = line->data;
306 column_data->author = commit->author;
307 column_data->date = &commit->time;
308 column_data->id = commit->id;
309 if (state->reflogs)
310 column_data->reflog = state->reflog[line->lineno - 1];
312 column_data->commit_title = commit->title;
313 if (state->with_graph) {
314 column_data->graph = state->graph;
315 column_data->graph_canvas = &commit->graph;
318 column_data->refs = main_get_commit_refs(line, commit);
320 return TRUE;
323 static bool
324 main_add_reflog(struct view *view, struct main_state *state, char *reflog)
326 char *end = strchr(reflog, ' ');
327 int id_width;
329 if (!end)
330 return FALSE;
331 *end = 0;
333 if (!realloc_reflogs(&state->reflog, state->reflogs, 1)
334 || !(reflog = strdup(reflog)))
335 return FALSE;
337 state->reflog[state->reflogs++] = reflog;
338 id_width = strlen(reflog);
339 if (state->reflog_width < id_width) {
340 struct view_column *column = get_view_column(view, VIEW_COLUMN_ID);
342 state->reflog_width = id_width;
343 if (column && column->opt.id.display)
344 view->force_redraw = TRUE;
347 return TRUE;
350 /* Reads git log --pretty=raw output and parses it into the commit struct. */
351 bool
352 main_read(struct view *view, struct buffer *buf)
354 struct main_state *state = view->private;
355 struct graph *graph = state->graph;
356 enum line_type type;
357 struct commit *commit = &state->current;
358 char *line;
360 if (!buf) {
361 main_flush_commit(view, commit);
363 if (failed_to_load_initial_view(view))
364 die("No revisions match the given arguments.");
365 if (view->lines > 0) {
366 struct commit *last = view->line[view->lines - 1].data;
368 view->line[view->lines - 1].dirty = 1;
369 if (!last->author) {
370 view->lines--;
371 free(last);
375 if (state->graph)
376 state->graph->done_rendering(graph);
377 return TRUE;
380 line = buf->data;
381 type = get_line_type(line);
382 if (type == LINE_COMMIT) {
383 bool is_boundary;
384 char *author;
386 state->in_header = TRUE;
387 line += STRING_SIZE("commit ");
388 is_boundary = *line == '-';
389 while (*line && !isalnum(*line))
390 line++;
392 main_flush_commit(view, commit);
394 author = io_memchr(buf, line, 0);
396 if (state->first_parent) {
397 char *parent = strchr(line, ' ');
398 char *parent_end = parent ? strchr(parent + 1, ' ') : NULL;
400 if (parent_end)
401 *parent_end = 0;
404 main_register_commit(view, &state->current, line, is_boundary);
406 if (author) {
407 char *title = io_memchr(buf, author, 0);
409 parse_author_line(author, &commit->author, &commit->time);
410 if (state->with_graph)
411 graph->render_parents(graph, &commit->graph);
412 if (title)
413 main_add_commit(view, LINE_MAIN_COMMIT, commit, title, FALSE);
416 return TRUE;
419 if (!*commit->id)
420 return TRUE;
422 /* Empty line separates the commit header from the log itself. */
423 if (*line == '\0')
424 state->in_header = FALSE;
426 switch (type) {
427 case LINE_PP_REFLOG:
428 if (!main_add_reflog(view, state, line + STRING_SIZE("Reflog: ")))
429 return FALSE;
430 break;
432 case LINE_PP_REFLOGMSG:
433 line += STRING_SIZE("Reflog message: ");
434 string_ncopy(state->reflogmsg, line, strlen(line));
435 break;
437 case LINE_PARENT:
438 if (state->with_graph)
439 graph->add_parent(graph, line + STRING_SIZE("parent "));
440 break;
442 case LINE_AUTHOR:
443 parse_author_line(line + STRING_SIZE("author "),
444 &commit->author, &commit->time);
445 if (state->with_graph)
446 graph->render_parents(graph, &commit->graph);
447 break;
449 default:
450 /* Fill in the commit title if it has not already been set. */
451 if (*commit->title)
452 break;
454 /* Skip lines in the commit header. */
455 if (state->in_header)
456 break;
458 /* Require titles to start with a non-space character at the
459 * offset used by git log. */
460 if (strncmp(line, " ", 4))
461 break;
462 line += 4;
463 /* Well, if the title starts with a whitespace character,
464 * try to be forgiving. Otherwise we end up with no title. */
465 while (isspace(*line))
466 line++;
467 if (*line == '\0')
468 break;
469 if (*state->reflogmsg)
470 line = state->reflogmsg;
471 main_add_commit(view, LINE_MAIN_COMMIT, commit, line, FALSE);
474 return TRUE;
477 enum request
478 main_request(struct view *view, enum request request, struct line *line)
480 enum open_flags flags = (view_is_displayed(view) && request != REQ_VIEW_DIFF)
481 ? OPEN_SPLIT : OPEN_DEFAULT;
483 switch (request) {
484 case REQ_NEXT:
485 case REQ_PREVIOUS:
486 if (view_is_displayed(view) && display[0] != view)
487 return request;
488 /* Do not pass navigation requests to the branch view
489 * when the main view is maximized. (GH #38) */
490 return request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
492 case REQ_VIEW_DIFF:
493 case REQ_ENTER:
494 if (view_is_displayed(view) && display[0] != view)
495 maximize_view(view, TRUE);
497 if (line->type == LINE_STAT_UNSTAGED
498 || line->type == LINE_STAT_STAGED)
499 open_stage_view(view, NULL, line->type, flags);
500 else
501 open_diff_view(view, flags);
502 break;
504 case REQ_REFRESH:
505 load_refs(TRUE);
506 refresh_view(view);
507 break;
509 default:
510 return request;
513 return REQ_NONE;
516 /* Update the env from last ref to first using recursion. */
517 static void
518 main_update_env(struct view *view, const struct ref *ref)
520 if (ref->next)
521 main_update_env(view, ref->next);
522 ref_update_env(view->env, ref, !ref->next);
525 void
526 main_select(struct view *view, struct line *line)
528 struct commit *commit = line->data;
530 if (line->type == LINE_STAT_STAGED || line->type == LINE_STAT_UNSTAGED) {
531 string_ncopy(view->ref, commit->title, strlen(commit->title));
532 status_stage_info(view->env->status, line->type, NULL);
533 } else {
534 const struct ref *ref = main_get_commit_refs(line, commit);
536 string_copy_rev(view->ref, commit->id);
537 if (ref)
538 main_update_env(view, ref);
540 string_copy_rev(view->env->commit, commit->id);
543 static struct view_ops main_ops = {
544 "commit",
545 argv_env.head,
546 VIEW_SEND_CHILD_ENTER | VIEW_FILE_FILTER | VIEW_LOG_LIKE | VIEW_REFRESH,
547 sizeof(struct main_state),
548 main_open,
549 main_read,
550 view_column_draw,
551 main_request,
552 view_column_grep,
553 main_select,
554 main_done,
555 view_column_bit(AUTHOR) | view_column_bit(COMMIT_TITLE) |
556 view_column_bit(DATE) | view_column_bit(ID) |
557 view_column_bit(LINE_NUMBER),
558 main_get_column_data,
561 DEFINE_VIEW(main);
563 /* vim: set ts=8 sw=8 noexpandtab: */