Add reflog member to the column_data struct
[tig.git] / src / main.c
blob23038053b65bc8f1779d6228e3dd51f22ab98d17
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/repo.h"
15 #include "tig/options.h"
16 #include "tig/parse.h"
17 #include "tig/graph.h"
18 #include "tig/display.h"
19 #include "tig/view.h"
20 #include "tig/draw.h"
21 #include "tig/git.h"
22 #include "tig/status.h"
23 #include "tig/main.h"
24 #include "tig/diff.h"
27 * Main view backend
30 DEFINE_ALLOCATOR(realloc_reflogs, char *, 32)
32 static void
33 main_register_commit(struct view *view, struct commit *commit, const char *ids, bool is_boundary)
35 struct main_state *state = view->private;
37 string_copy_rev(commit->id, ids);
38 if (state->with_graph)
39 graph_add_commit(&state->graph, &commit->graph, commit->id, ids, is_boundary);
42 static struct commit *
43 main_add_commit(struct view *view, enum line_type type, struct commit *template,
44 const char *title, bool custom)
46 struct main_state *state = view->private;
47 size_t titlelen = strlen(title);
48 struct commit *commit;
49 char buf[SIZEOF_STR / 2];
50 struct line *line;
52 /* FIXME: More graceful handling of titles; append "..." to
53 * shortened titles, etc. */
54 string_expand(buf, sizeof(buf), title, 1);
55 title = buf;
56 titlelen = strlen(title);
58 line = add_line_alloc(view, &commit, type, titlelen, custom);
59 if (!line)
60 return NULL;
62 *commit = *template;
63 strncpy(commit->title, title, titlelen);
64 state->graph.canvas = &commit->graph;
65 memset(template, 0, sizeof(*template));
66 state->reflogmsg[0] = 0;
68 view_column_info_update(view, line);
69 return commit;
72 static inline void
73 main_flush_commit(struct view *view, struct commit *commit)
75 if (*commit->id)
76 main_add_commit(view, LINE_MAIN_COMMIT, commit, "", FALSE);
79 static bool
80 main_has_changes(const char *argv[])
82 struct io io;
84 if (!io_exec(&io, IO_BG, NULL, opt_env, argv, -1))
85 return FALSE;
86 io_done(&io);
87 return io.status == 1;
90 static void
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 commit commit = {};
96 struct timeval now;
97 struct timezone tz;
99 if (!parent)
100 return;
102 string_copy_rev(ids + STRING_SIZE(NULL_ID " "), parent);
104 if (!gettimeofday(&now, &tz)) {
105 commit.time.tz = tz.tz_minuteswest * 60;
106 commit.time.sec = now.tv_sec - commit.time.tz;
109 commit.author = &unknown_ident;
110 main_register_commit(view, &commit, ids, FALSE);
111 if (main_add_commit(view, type, &commit, title, TRUE) && state->with_graph)
112 graph_render_parents(&state->graph);
115 static void
116 main_add_changes_commits(struct view *view, struct main_state *state, const char *parent)
118 const char *staged_argv[] = { GIT_DIFF_STAGED_FILES("--quiet") };
119 const char *unstaged_argv[] = { GIT_DIFF_UNSTAGED_FILES("--quiet") };
120 const char *staged_parent = NULL_ID;
121 const char *unstaged_parent = parent;
123 if (!is_head_commit(parent))
124 return;
126 state->added_changes_commits = TRUE;
128 io_run_bg(update_index_argv);
130 if (!main_has_changes(unstaged_argv)) {
131 unstaged_parent = NULL;
132 staged_parent = parent;
135 if (!main_has_changes(staged_argv)) {
136 staged_parent = NULL;
139 main_add_changes_commit(view, LINE_STAT_STAGED, staged_parent, "Staged changes");
140 main_add_changes_commit(view, LINE_STAT_UNSTAGED, unstaged_parent, "Unstaged changes");
143 static bool
144 main_check_argv(struct view *view, const char *argv[])
146 struct main_state *state = view->private;
147 bool with_reflog = FALSE;
148 int i;
150 for (i = 0; argv[i]; i++) {
151 const char *arg = argv[i];
152 struct rev_flags rev_flags = {};
154 if (!strcmp(arg, "--graph")) {
155 struct view_column *column = get_view_column(view, VIEW_COLUMN_COMMIT_TITLE);
157 if (column) {
158 column->opt.commit_title.graph = TRUE;
159 if (opt_commit_order != COMMIT_ORDER_REVERSE)
160 state->with_graph = TRUE;
162 argv[i] = "";
163 continue;
166 if (!argv_parse_rev_flag(arg, &rev_flags))
167 continue;
169 if (rev_flags.with_reflog)
170 with_reflog = TRUE;
171 if (!rev_flags.with_graph)
172 state->with_graph = FALSE;
173 arg += rev_flags.search_offset;
174 if (*arg && !*view->env->search)
175 string_ncopy(view->env->search, arg, strlen(arg));
178 return with_reflog;
181 static bool
182 main_open(struct view *view, enum open_flags flags)
184 const char *pretty_custom_argv[] = {
185 GIT_MAIN_LOG_CUSTOM(encoding_arg, commit_order_arg(), "%(cmdlineargs)", "%(revargs)", "%(fileargs)")
187 const char *pretty_raw_argv[] = {
188 GIT_MAIN_LOG_RAW(encoding_arg, commit_order_arg(), "%(cmdlineargs)", "%(revargs)", "%(fileargs)")
190 struct main_state *state = view->private;
191 const char **main_argv = pretty_custom_argv;
192 struct view_column *column;
194 column = get_view_column(view, VIEW_COLUMN_COMMIT_TITLE);
195 state->with_graph = column && column->opt.commit_title.graph &&
196 opt_commit_order != COMMIT_ORDER_REVERSE;
198 if (opt_rev_argv && main_check_argv(view, opt_rev_argv))
199 main_argv = pretty_raw_argv;
201 if (open_in_pager_mode(flags)) {
202 state->added_changes_commits = TRUE;
203 state->with_graph = FALSE;
206 return begin_update(view, NULL, main_argv, flags);
209 void
210 main_done(struct view *view)
212 struct main_state *state = view->private;
213 int i;
215 for (i = 0; i < view->lines; i++) {
216 struct commit *commit = view->line[i].data;
218 free(commit->graph.symbols);
221 for (i = 0; i < state->reflogs; i++)
222 free(state->reflog[i]);
223 free(state->reflog);
226 #define main_check_commit_refs(line) !((line)->no_commit_refs)
227 #define main_mark_no_commit_refs(line) (((struct line *) (line))->no_commit_refs = 1)
229 static inline struct ref_list *
230 main_get_commit_refs(const struct line *line, struct commit *commit)
232 struct ref_list *refs = NULL;
234 if (main_check_commit_refs(line) && !(refs = get_ref_list(commit->id)))
235 main_mark_no_commit_refs(line);
237 return refs;
240 bool
241 main_get_column_data(struct view *view, const struct line *line, struct view_column_data *column_data)
243 struct main_state *state = view->private;
244 struct commit *commit = line->data;
245 struct ref_list *refs = NULL;
247 column_data->author = commit->author;
248 column_data->date = &commit->time;
249 column_data->id = commit->id;
250 if (state->reflogs)
251 column_data->reflog = state->reflog[line->lineno - 1];
253 column_data->commit_title = commit->title;
254 if (state->with_graph)
255 column_data->graph = &commit->graph;
257 if ((refs = main_get_commit_refs(line, commit)))
258 column_data->refs = refs;
260 return TRUE;
263 static bool
264 main_add_reflog(struct view *view, struct main_state *state, char *reflog)
266 char *end = strchr(reflog, ' ');
267 int id_width;
269 if (!end)
270 return FALSE;
271 *end = 0;
273 if (!realloc_reflogs(&state->reflog, state->reflogs, 1)
274 || !(reflog = strdup(reflog)))
275 return FALSE;
277 state->reflog[state->reflogs++] = reflog;
278 id_width = strlen(reflog);
279 if (state->reflog_width < id_width) {
280 struct view_column *column = get_view_column(view, VIEW_COLUMN_ID);
282 state->reflog_width = id_width;
283 if (column && column->opt.id.show)
284 view->force_redraw = TRUE;
287 return TRUE;
290 /* Reads git log --pretty=raw output and parses it into the commit struct. */
291 bool
292 main_read(struct view *view, char *line)
294 struct main_state *state = view->private;
295 struct graph *graph = &state->graph;
296 enum line_type type;
297 struct commit *commit = &state->current;
299 if (!line) {
300 main_flush_commit(view, commit);
302 if (failed_to_load_initial_view(view))
303 die("No revisions match the given arguments.");
304 if (view->lines > 0) {
305 struct commit *last = view->line[view->lines - 1].data;
307 view->line[view->lines - 1].dirty = 1;
308 if (!last->author) {
309 view->lines--;
310 free(last);
314 if (state->with_graph)
315 done_graph(graph);
316 return TRUE;
319 type = get_line_type(line);
320 if (type == LINE_COMMIT) {
321 bool is_boundary;
322 char *author;
324 state->in_header = TRUE;
325 line += STRING_SIZE("commit ");
326 is_boundary = *line == '-';
327 while (*line && !isalnum(*line))
328 line++;
330 if (!state->added_changes_commits && opt_show_changes && repo.is_inside_work_tree)
331 main_add_changes_commits(view, state, line);
332 else
333 main_flush_commit(view, commit);
335 main_register_commit(view, &state->current, line, is_boundary);
337 author = io_memchr(&view->io, line, 0);
338 if (author) {
339 char *title = io_memchr(&view->io, author, 0);
341 parse_author_line(author, &commit->author, &commit->time);
342 if (state->with_graph)
343 graph_render_parents(graph);
344 if (title)
345 main_add_commit(view, LINE_MAIN_COMMIT, commit, title, FALSE);
348 return TRUE;
351 if (!*commit->id)
352 return TRUE;
354 /* Empty line separates the commit header from the log itself. */
355 if (*line == '\0')
356 state->in_header = FALSE;
358 switch (type) {
359 case LINE_PP_REFLOG:
360 if (!main_add_reflog(view, state, line + STRING_SIZE("Reflog: ")))
361 return FALSE;
362 break;
364 case LINE_PP_REFLOGMSG:
365 line += STRING_SIZE("Reflog message: ");
366 string_ncopy(state->reflogmsg, line, strlen(line));
367 break;
369 case LINE_PARENT:
370 if (state->with_graph && !graph->has_parents)
371 graph_add_parent(graph, line + STRING_SIZE("parent "));
372 break;
374 case LINE_AUTHOR:
375 parse_author_line(line + STRING_SIZE("author "),
376 &commit->author, &commit->time);
377 if (state->with_graph)
378 graph_render_parents(graph);
379 break;
381 default:
382 /* Fill in the commit title if it has not already been set. */
383 if (*commit->title)
384 break;
386 /* Skip lines in the commit header. */
387 if (state->in_header)
388 break;
390 /* Require titles to start with a non-space character at the
391 * offset used by git log. */
392 if (strncmp(line, " ", 4))
393 break;
394 line += 4;
395 /* Well, if the title starts with a whitespace character,
396 * try to be forgiving. Otherwise we end up with no title. */
397 while (isspace(*line))
398 line++;
399 if (*line == '\0')
400 break;
401 if (*state->reflogmsg)
402 line = state->reflogmsg;
403 main_add_commit(view, LINE_MAIN_COMMIT, commit, line, FALSE);
406 return TRUE;
409 enum request
410 main_request(struct view *view, enum request request, struct line *line)
412 enum open_flags flags = (view_is_displayed(view) && request != REQ_VIEW_DIFF)
413 ? OPEN_SPLIT : OPEN_DEFAULT;
415 switch (request) {
416 case REQ_NEXT:
417 case REQ_PREVIOUS:
418 if (view_is_displayed(view) && display[0] != view)
419 return request;
420 /* Do not pass navigation requests to the branch view
421 * when the main view is maximized. (GH #38) */
422 return request == REQ_NEXT ? REQ_MOVE_DOWN : REQ_MOVE_UP;
424 case REQ_VIEW_DIFF:
425 case REQ_ENTER:
426 if (view_is_displayed(view) && display[0] != view)
427 maximize_view(view, TRUE);
429 if (line->type == LINE_STAT_UNSTAGED
430 || line->type == LINE_STAT_STAGED) {
431 struct view *diff = &diff_view;
432 const char *diff_staged_argv[] = {
433 GIT_DIFF_STAGED(encoding_arg,
434 diff_context_arg(),
435 ignore_space_arg(), NULL, NULL)
437 const char *diff_unstaged_argv[] = {
438 GIT_DIFF_UNSTAGED(encoding_arg,
439 diff_context_arg(),
440 ignore_space_arg(), NULL, NULL)
442 const char **diff_argv = line->type == LINE_STAT_STAGED
443 ? diff_staged_argv : diff_unstaged_argv;
445 open_argv(view, diff, diff_argv, NULL, flags);
446 break;
449 open_diff_view(view, flags);
450 break;
452 case REQ_REFRESH:
453 load_refs(TRUE);
454 refresh_view(view);
455 break;
457 case REQ_JUMP_COMMIT:
459 int lineno;
461 for (lineno = 0; lineno < view->lines; lineno++) {
462 struct commit *commit = view->line[lineno].data;
464 if (!strncasecmp(commit->id, view->env->search, strlen(view->env->search))) {
465 select_view_line(view, lineno);
466 report_clear();
467 return REQ_NONE;
471 report("Unable to find commit '%s'", view->env->search);
472 break;
474 default:
475 return request;
478 return REQ_NONE;
481 static struct ref *
482 main_get_commit_branch(struct line *line, struct commit *commit)
484 struct ref_list *list = main_get_commit_refs(line, commit);
485 struct ref *branch = NULL;
486 size_t i;
488 for (i = 0; list && i < list->size; i++) {
489 struct ref *ref = list->refs[i];
491 switch (get_line_type_from_ref(ref)) {
492 case LINE_MAIN_HEAD:
493 case LINE_MAIN_REF:
494 /* Always prefer local branches. */
495 return ref;
497 default:
498 branch = ref;
502 return branch;
505 void
506 main_select(struct view *view, struct line *line)
508 struct commit *commit = line->data;
510 if (line->type == LINE_STAT_STAGED || line->type == LINE_STAT_UNSTAGED) {
511 string_ncopy(view->ref, commit->title, strlen(commit->title));
512 } else {
513 struct ref *branch = main_get_commit_branch(line, commit);
515 if (branch)
516 string_copy_rev(view->env->branch, branch->name);
517 string_copy_rev(view->ref, commit->id);
519 string_copy_rev(view->env->commit, commit->id);
522 static struct view_ops main_ops = {
523 "commit",
524 argv_env.head,
525 VIEW_SEND_CHILD_ENTER | VIEW_FILE_FILTER | VIEW_LOG_LIKE | VIEW_REFRESH,
526 sizeof(struct main_state),
527 main_open,
528 main_read,
529 view_column_draw,
530 main_request,
531 view_column_grep,
532 main_select,
533 main_done,
534 view_column_bit(AUTHOR) | view_column_bit(COMMIT_TITLE) |
535 view_column_bit(DATE) | view_column_bit(ID) |
536 view_column_bit(LINE_NUMBER),
537 main_get_column_data,
540 DEFINE_VIEW(main);
542 /* vim: set ts=8 sw=8 noexpandtab: */