tig-2.2
[tig.git] / src / main.c
blobb3d0c0a5ad445b3a0e20dcae189475ff549dc3c3
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 bool
35 main_status_exists(struct view *view, enum line_type type)
37 struct main_state *state;
39 refresh_view(view);
41 state = view->private;
42 state->goto_line_type = type;
43 if (type == LINE_STAT_STAGED && state->add_changes_staged)
44 return true;
45 if (type == LINE_STAT_UNSTAGED && state->add_changes_unstaged)
46 return true;
48 return false;
51 static bool main_add_changes(struct view *view, struct main_state *state, const char *parent);
53 static void
54 main_register_commit(struct view *view, struct commit *commit, const char *ids, bool is_boundary)
56 struct main_state *state = view->private;
57 struct graph *graph = state->graph;
59 string_copy_rev(commit->id, ids);
61 /* FIXME: lazily check index state here instead of in main_open. */
62 if ((state->add_changes_unstaged || state->add_changes_staged) && is_head_commit(commit->id)) {
63 main_add_changes(view, state, ids);
64 state->add_changes_unstaged = state->add_changes_staged = false;
67 if (state->with_graph)
68 graph->add_commit(graph, &commit->graph, commit->id, ids, is_boundary);
71 static struct commit *
72 main_add_commit(struct view *view, enum line_type type, struct commit *template,
73 const char *title, bool custom)
75 struct main_state *state = view->private;
76 size_t titlelen;
77 struct commit *commit;
78 char buf[SIZEOF_STR / 2];
79 struct line *line;
81 /* FIXME: More graceful handling of titles; append "..." to
82 * shortened titles, etc. */
83 string_expand(buf, sizeof(buf), title, strlen(title), 1);
84 title = buf;
85 titlelen = strlen(title);
87 line = add_line_alloc(view, &commit, type, titlelen, custom);
88 if (!line)
89 return NULL;
91 *commit = *template;
92 strncpy(commit->title, title, titlelen);
93 memset(template, 0, sizeof(*template));
94 state->reflogmsg[0] = 0;
96 view_column_info_update(view, line);
97 return commit;
100 static inline void
101 main_flush_commit(struct view *view, struct commit *commit)
103 if (*commit->id)
104 main_add_commit(view, LINE_MAIN_COMMIT, commit, "", false);
107 static bool
108 main_add_changes_commit(struct view *view, enum line_type type, const char *parent, const char *title)
110 char ids[SIZEOF_STR] = NULL_ID " ";
111 struct main_state *state = view->private;
112 struct graph *graph = state->graph;
113 struct commit commit = {{0}};
114 struct timeval now;
115 struct timezone tz;
117 if (!parent)
118 return true;
120 if (*parent)
121 string_copy_rev(ids + STRING_SIZE(NULL_ID " "), parent);
122 else
123 ids[STRING_SIZE(NULL_ID)] = 0;
125 if (!time_now(&now, &tz)) {
126 commit.time.tz = tz.tz_minuteswest * 60;
127 commit.time.sec = now.tv_sec - commit.time.tz;
130 commit.author = &unknown_ident;
131 main_register_commit(view, &commit, ids, false);
132 if (state->with_graph && *parent)
133 graph->render_parents(graph, &commit.graph);
135 if (!main_add_commit(view, type, &commit, title, true))
136 return false;
138 if (state->goto_line_type == type)
139 select_view_line(view, view->lines - 1);
141 return true;
144 static bool
145 main_check_index(struct view *view, struct main_state *state)
147 struct index_diff diff;
149 if (!index_diff(&diff, false, false))
150 return false;
152 if (!diff.unstaged) {
153 watch_apply(&view->watch, WATCH_INDEX_UNSTAGED_NO);
154 } else {
155 watch_apply(&view->watch, WATCH_INDEX_UNSTAGED_YES);
156 state->add_changes_unstaged = true;
159 if (!diff.staged) {
160 watch_apply(&view->watch, WATCH_INDEX_STAGED_NO);
161 } else {
162 watch_apply(&view->watch, WATCH_INDEX_STAGED_YES);
163 state->add_changes_staged = true;
166 return true;
169 static bool
170 main_add_changes(struct view *view, struct main_state *state, const char *parent)
172 const char *staged_parent = parent;
173 const char *unstaged_parent = NULL_ID;
175 if (!state->add_changes_staged) {
176 staged_parent = NULL;
177 unstaged_parent = parent;
180 if (!state->add_changes_unstaged) {
181 unstaged_parent = NULL;
184 return main_add_changes_commit(view, LINE_STAT_UNSTAGED, unstaged_parent, "Unstaged changes")
185 && main_add_changes_commit(view, LINE_STAT_STAGED, staged_parent, "Staged changes");
188 static bool
189 main_check_argv(struct view *view, const char *argv[])
191 struct main_state *state = view->private;
192 bool with_reflog = false;
193 int i;
195 for (i = 0; argv[i]; i++) {
196 const char *arg = argv[i];
197 struct rev_flags rev_flags = {0};
199 if (!strcmp(arg, "--graph")) {
200 struct view_column *column = get_view_column(view, VIEW_COLUMN_COMMIT_TITLE);
202 if (column) {
203 column->opt.commit_title.graph = true;
204 if (opt_commit_order != COMMIT_ORDER_REVERSE)
205 state->with_graph = true;
207 argv[i] = "";
208 continue;
211 if (!strcmp(arg, "--merge")) {
212 argv_append(&opt_rev_args, "--boundary");
213 continue;
216 if (!strcmp(arg, "--first-parent"))
217 state->first_parent = true;
219 if (!argv_parse_rev_flag(arg, &rev_flags))
220 continue;
222 if (rev_flags.with_reflog)
223 with_reflog = true;
224 if (!rev_flags.with_graph)
225 state->with_graph = false;
226 arg += rev_flags.search_offset;
227 if (*arg && !*view->env->search)
228 string_ncopy(view->env->search, arg, strlen(arg));
231 return with_reflog;
234 static enum graph_display
235 main_with_graph(struct view *view, struct view_column *column, enum open_flags flags)
237 return column && opt_commit_order != COMMIT_ORDER_REVERSE && !open_in_pager_mode(flags)
238 ? column->opt.commit_title.graph : GRAPH_DISPLAY_NO;
241 static bool
242 main_open(struct view *view, enum open_flags flags)
244 struct view_column *commit_title_column = get_view_column(view, VIEW_COLUMN_COMMIT_TITLE);
245 enum graph_display graph_display = main_with_graph(view, commit_title_column, flags);
246 const char *pretty_custom_argv[] = {
247 GIT_MAIN_LOG(encoding_arg, commit_order_arg_with_graph(graph_display),
248 "%(mainargs)", "%(cmdlineargs)", "%(revargs)", "%(fileargs)",
249 log_custom_pretty_arg())
251 const char *pretty_raw_argv[] = {
252 GIT_MAIN_LOG_RAW(encoding_arg, commit_order_arg_with_graph(graph_display),
253 "%(mainargs)", "%(cmdlineargs)", "%(revargs)", "%(fileargs)")
255 struct main_state *state = view->private;
256 const char **main_argv = pretty_custom_argv;
257 enum watch_trigger changes_triggers = WATCH_NONE;
259 if (opt_show_changes && repo.is_inside_work_tree)
260 changes_triggers |= WATCH_INDEX;
262 state->with_graph = graph_display != GRAPH_DISPLAY_NO;
264 if (opt_rev_args && main_check_argv(view, opt_rev_args))
265 main_argv = pretty_raw_argv;
267 if (state->with_graph) {
268 state->graph = init_graph(commit_title_column->opt.commit_title.graph);
269 if (!state->graph)
270 return false;
273 if (open_in_pager_mode(flags)) {
274 changes_triggers = WATCH_NONE;
277 /* This calls reset_view() so must be before adding changes commits. */
278 if (!begin_update(view, NULL, main_argv, flags))
279 return false;
281 /* Register watch before changes commits are added to record the
282 * start. */
283 if (view_can_refresh(view))
284 watch_register(&view->watch, WATCH_HEAD | WATCH_REFS | changes_triggers);
286 if (changes_triggers)
287 main_check_index(view, state);
289 return true;
292 void
293 main_done(struct view *view)
295 struct main_state *state = view->private;
296 int i;
298 for (i = 0; i < view->lines; i++) {
299 struct commit *commit = view->line[i].data;
301 free(commit->graph.symbols);
304 if (state->graph)
305 state->graph->done(state->graph);
307 for (i = 0; i < state->reflogs; i++)
308 free(state->reflog[i]);
309 free(state->reflog);
312 #define main_check_commit_refs(line) !((line)->no_commit_refs)
313 #define main_mark_no_commit_refs(line) (((struct line *) (line))->no_commit_refs = 1)
315 static inline const struct ref *
316 main_get_commit_refs(const struct line *line, struct commit *commit)
318 const struct ref *refs = NULL;
320 if (main_check_commit_refs(line) && !(refs = get_ref_list(commit->id)))
321 main_mark_no_commit_refs(line);
323 return refs;
326 bool
327 main_get_column_data(struct view *view, const struct line *line, struct view_column_data *column_data)
329 struct main_state *state = view->private;
330 struct commit *commit = line->data;
332 column_data->author = commit->author;
333 column_data->date = &commit->time;
334 column_data->id = commit->id;
335 if (state->reflogs)
336 column_data->reflog = state->reflog[line->lineno - 1];
338 column_data->commit_title = commit->title;
339 if (state->with_graph) {
340 column_data->graph = state->graph;
341 column_data->graph_canvas = &commit->graph;
344 column_data->refs = main_get_commit_refs(line, commit);
346 return true;
349 static bool
350 main_add_reflog(struct view *view, struct main_state *state, char *reflog)
352 char *end = strchr(reflog, ' ');
353 int id_width;
355 if (!end)
356 return false;
357 *end = 0;
359 if (!realloc_reflogs(&state->reflog, state->reflogs, 1)
360 || !(reflog = strdup(reflog)))
361 return false;
363 state->reflog[state->reflogs++] = reflog;
364 id_width = strlen(reflog);
365 if (state->reflog_width < id_width) {
366 struct view_column *column = get_view_column(view, VIEW_COLUMN_ID);
368 state->reflog_width = id_width;
369 if (column && column->opt.id.display)
370 view->force_redraw = true;
373 return true;
376 /* Reads git log --pretty=raw output and parses it into the commit struct. */
377 bool
378 main_read(struct view *view, struct buffer *buf, bool force_stop)
380 struct main_state *state = view->private;
381 struct graph *graph = state->graph;
382 enum line_type type;
383 struct commit *commit = &state->current;
384 char *line;
386 if (!buf) {
387 main_flush_commit(view, commit);
389 if (!force_stop && failed_to_load_initial_view(view))
390 die("No revisions match the given arguments.");
391 if (view->lines > 0) {
392 struct commit *last = view->line[view->lines - 1].data;
394 view->line[view->lines - 1].dirty = 1;
395 if (!last->author) {
396 view->lines--;
397 free(last);
401 if (state->graph)
402 state->graph->done_rendering(graph);
403 return true;
406 line = buf->data;
407 type = get_line_type(line);
408 if (type == LINE_COMMIT) {
409 bool is_boundary;
410 char *author;
412 state->in_header = true;
413 line += STRING_SIZE("commit ");
414 is_boundary = *line == '-';
415 while (*line && !isalnum(*line))
416 line++;
418 main_flush_commit(view, commit);
420 author = io_memchr(buf, line, 0);
422 if (state->first_parent) {
423 char *parent = strchr(line, ' ');
424 char *parent_end = parent ? strchr(parent + 1, ' ') : NULL;
426 if (parent_end)
427 *parent_end = 0;
430 main_register_commit(view, &state->current, line, is_boundary);
432 if (author) {
433 char *title = io_memchr(buf, author, 0);
435 parse_author_line(author, &commit->author, &commit->time);
436 if (state->with_graph)
437 graph->render_parents(graph, &commit->graph);
438 if (title)
439 main_add_commit(view, LINE_MAIN_COMMIT, commit, title, false);
442 return true;
445 if (!*commit->id)
446 return true;
448 /* Empty line separates the commit header from the log itself. */
449 if (*line == '\0')
450 state->in_header = false;
452 switch (type) {
453 case LINE_PP_REFLOG:
454 if (!main_add_reflog(view, state, line + STRING_SIZE("Reflog: ")))
455 return false;
456 break;
458 case LINE_PP_REFLOGMSG:
459 line += STRING_SIZE("Reflog message: ");
460 string_ncopy(state->reflogmsg, line, strlen(line));
461 break;
463 case LINE_PARENT:
464 if (state->with_graph)
465 graph->add_parent(graph, line + STRING_SIZE("parent "));
466 break;
468 case LINE_AUTHOR:
469 parse_author_line(line + STRING_SIZE("author "),
470 &commit->author, &commit->time);
471 if (state->with_graph)
472 graph->render_parents(graph, &commit->graph);
473 break;
475 default:
476 /* Fill in the commit title if it has not already been set. */
477 if (*commit->title)
478 break;
480 /* Skip lines in the commit header. */
481 if (state->in_header)
482 break;
484 /* Require titles to start with a non-space character at the
485 * offset used by git log. */
486 if (strncmp(line, " ", 4))
487 break;
488 line += 4;
489 /* Well, if the title starts with a whitespace character,
490 * try to be forgiving. Otherwise we end up with no title. */
491 while (isspace(*line))
492 line++;
493 if (*line == '\0')
494 break;
495 if (*state->reflogmsg)
496 line = state->reflogmsg;
497 main_add_commit(view, LINE_MAIN_COMMIT, commit, line, false);
500 return true;
503 enum request
504 main_request(struct view *view, enum request request, struct line *line)
506 enum open_flags flags = (view_is_displayed(view) && request != REQ_VIEW_DIFF)
507 ? OPEN_SPLIT : OPEN_DEFAULT;
509 switch (request) {
510 case REQ_NEXT:
511 case REQ_PREVIOUS:
512 if (view_is_displayed(view) && display[0] != view)
513 return request;
514 /* Do not pass navigation requests to the branch view
515 * when the main view is maximized. (GH #38) */
516 return request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
518 case REQ_VIEW_DIFF:
519 case REQ_ENTER:
520 if (view_is_displayed(view) && display[0] != view)
521 maximize_view(view, true);
523 if (line->type == LINE_STAT_UNSTAGED
524 || line->type == LINE_STAT_STAGED)
525 open_stage_view(view, NULL, line->type, flags);
526 else
527 open_diff_view(view, flags);
528 break;
530 case REQ_REFRESH:
531 load_refs(true);
532 refresh_view(view);
533 break;
535 case REQ_PARENT:
536 goto_id(view, "%(commit)^", true, false);
537 break;
539 default:
540 return request;
543 return REQ_NONE;
546 void
547 main_select(struct view *view, struct line *line)
549 struct commit *commit = line->data;
551 if (line->type == LINE_STAT_STAGED || line->type == LINE_STAT_UNSTAGED) {
552 string_ncopy(view->ref, commit->title, strlen(commit->title));
553 status_stage_info(view->env->status, line->type, NULL);
554 } else {
555 const struct ref *ref = main_get_commit_refs(line, commit);
557 string_copy_rev(view->ref, commit->id);
558 if (ref)
559 ref_update_env(view->env, ref, true);
561 string_copy_rev(view->env->commit, commit->id);
564 static struct view_ops main_ops = {
565 "commit",
566 argv_env.head,
567 VIEW_SEND_CHILD_ENTER | VIEW_FILE_FILTER | VIEW_LOG_LIKE | VIEW_REFRESH,
568 sizeof(struct main_state),
569 main_open,
570 main_read,
571 view_column_draw,
572 main_request,
573 view_column_grep,
574 main_select,
575 main_done,
576 view_column_bit(AUTHOR) | view_column_bit(COMMIT_TITLE) |
577 view_column_bit(DATE) | view_column_bit(ID) |
578 view_column_bit(LINE_NUMBER),
579 main_get_column_data,
582 DEFINE_VIEW(main);
584 /* vim: set ts=8 sw=8 noexpandtab: */