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.
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"
23 #include "tig/status.h"
24 #include "tig/stage.h"
32 DEFINE_ALLOCATOR(realloc_reflogs
, char *, 32)
35 main_status_exists(struct view
*view
, enum line_type type
)
37 struct main_state
*state
;
41 state
= view
->private;
42 state
->goto_line_type
= type
;
43 if (type
== LINE_STAT_STAGED
&& state
->add_changes_staged
)
45 if (type
== LINE_STAT_UNSTAGED
&& state
->add_changes_unstaged
)
51 static bool main_add_changes(struct view
*view
, struct main_state
*state
, const char *parent
);
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;
77 struct commit
*commit
;
78 char buf
[SIZEOF_STR
/ 2];
81 /* FIXME: More graceful handling of titles; append "..." to
82 * shortened titles, etc. */
83 string_expand(buf
, sizeof(buf
), title
, 1);
85 titlelen
= strlen(title
);
87 line
= add_line_alloc(view
, &commit
, type
, titlelen
, custom
);
92 strncpy(commit
->title
, title
, titlelen
);
93 memset(template, 0, sizeof(*template));
94 state
->reflogmsg
[0] = 0;
96 view_column_info_update(view
, line
);
101 main_flush_commit(struct view
*view
, struct commit
*commit
)
104 main_add_commit(view
, LINE_MAIN_COMMIT
, commit
, "", FALSE
);
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}};
121 string_copy_rev(ids
+ STRING_SIZE(NULL_ID
" "), parent
);
123 ids
[STRING_SIZE(NULL_ID
)] = 0;
125 if (!gettimeofday(&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
))
138 if (state
->goto_line_type
== type
)
139 select_view_line(view
, view
->lines
- 1);
145 main_check_index(struct view
*view
, struct main_state
*state
)
147 struct index_diff diff
;
149 if (!index_diff(&diff
, FALSE
, FALSE
))
152 if (!diff
.unstaged
) {
153 watch_apply(&view
->watch
, WATCH_INDEX_UNSTAGED_NO
);
155 watch_apply(&view
->watch
, WATCH_INDEX_UNSTAGED_YES
);
156 state
->add_changes_unstaged
= TRUE
;
160 watch_apply(&view
->watch
, WATCH_INDEX_STAGED_NO
);
162 watch_apply(&view
->watch
, WATCH_INDEX_STAGED_YES
);
163 state
->add_changes_staged
= TRUE
;
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");
189 main_check_argv(struct view
*view
, const char *argv
[])
191 struct main_state
*state
= view
->private;
192 bool with_reflog
= FALSE
;
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
);
203 column
->opt
.commit_title
.graph
= TRUE
;
204 if (opt_commit_order
!= COMMIT_ORDER_REVERSE
)
205 state
->with_graph
= TRUE
;
211 if (!strcmp(arg
, "--first-parent"))
212 state
->first_parent
= TRUE
;
214 if (!argv_parse_rev_flag(arg
, &rev_flags
))
217 if (rev_flags
.with_reflog
)
219 if (!rev_flags
.with_graph
)
220 state
->with_graph
= FALSE
;
221 arg
+= rev_flags
.search_offset
;
222 if (*arg
&& !*view
->env
->search
)
223 string_ncopy(view
->env
->search
, arg
, strlen(arg
));
229 static enum graph_display
230 main_with_graph(struct view
*view
, struct view_column
*column
, enum open_flags flags
)
232 return column
&& opt_commit_order
!= COMMIT_ORDER_REVERSE
&& !open_in_pager_mode(flags
)
233 ? column
->opt
.commit_title
.graph
: GRAPH_DISPLAY_NO
;
237 main_open(struct view
*view
, enum open_flags flags
)
239 struct view_column
*commit_title_column
= get_view_column(view
, VIEW_COLUMN_COMMIT_TITLE
);
240 enum graph_display graph_display
= main_with_graph(view
, commit_title_column
, flags
);
241 const char *pretty_custom_argv
[] = {
242 GIT_MAIN_LOG_CUSTOM(encoding_arg
, commit_order_arg_with_graph(graph_display
),
243 "%(mainargs)", "%(cmdlineargs)", "%(revargs)", "%(fileargs)")
245 const char *pretty_raw_argv
[] = {
246 GIT_MAIN_LOG_RAW(encoding_arg
, commit_order_arg_with_graph(graph_display
),
247 "%(mainargs)", "%(cmdlineargs)", "%(revargs)", "%(fileargs)")
249 struct main_state
*state
= view
->private;
250 const char **main_argv
= pretty_custom_argv
;
251 enum watch_trigger changes_triggers
= WATCH_NONE
;
253 if (opt_show_changes
&& repo
.is_inside_work_tree
)
254 changes_triggers
|= WATCH_INDEX
;
256 state
->with_graph
= graph_display
!= GRAPH_DISPLAY_NO
;
258 if (opt_rev_args
&& main_check_argv(view
, opt_rev_args
))
259 main_argv
= pretty_raw_argv
;
261 if (state
->with_graph
) {
262 state
->graph
= init_graph(commit_title_column
->opt
.commit_title
.graph
);
267 if (open_in_pager_mode(flags
)) {
268 changes_triggers
= WATCH_NONE
;
271 /* This calls reset_view() so must be before adding changes commits. */
272 if (!begin_update(view
, NULL
, main_argv
, flags
))
275 /* Register watch before changes commits are added to record the
277 if (view_can_refresh(view
))
278 watch_register(&view
->watch
, WATCH_HEAD
| WATCH_REFS
| changes_triggers
);
280 if (changes_triggers
)
281 main_check_index(view
, state
);
287 main_done(struct view
*view
)
289 struct main_state
*state
= view
->private;
292 for (i
= 0; i
< view
->lines
; i
++) {
293 struct commit
*commit
= view
->line
[i
].data
;
295 free(commit
->graph
.symbols
);
299 state
->graph
->done(state
->graph
);
301 for (i
= 0; i
< state
->reflogs
; i
++)
302 free(state
->reflog
[i
]);
306 #define main_check_commit_refs(line) !((line)->no_commit_refs)
307 #define main_mark_no_commit_refs(line) (((struct line *) (line))->no_commit_refs = 1)
309 static inline const struct ref
*
310 main_get_commit_refs(const struct line
*line
, struct commit
*commit
)
312 const struct ref
*refs
= NULL
;
314 if (main_check_commit_refs(line
) && !(refs
= get_ref_list(commit
->id
)))
315 main_mark_no_commit_refs(line
);
321 main_get_column_data(struct view
*view
, const struct line
*line
, struct view_column_data
*column_data
)
323 struct main_state
*state
= view
->private;
324 struct commit
*commit
= line
->data
;
326 column_data
->author
= commit
->author
;
327 column_data
->date
= &commit
->time
;
328 column_data
->id
= commit
->id
;
330 column_data
->reflog
= state
->reflog
[line
->lineno
- 1];
332 column_data
->commit_title
= commit
->title
;
333 if (state
->with_graph
) {
334 column_data
->graph
= state
->graph
;
335 column_data
->graph_canvas
= &commit
->graph
;
338 column_data
->refs
= main_get_commit_refs(line
, commit
);
344 main_add_reflog(struct view
*view
, struct main_state
*state
, char *reflog
)
346 char *end
= strchr(reflog
, ' ');
353 if (!realloc_reflogs(&state
->reflog
, state
->reflogs
, 1)
354 || !(reflog
= strdup(reflog
)))
357 state
->reflog
[state
->reflogs
++] = reflog
;
358 id_width
= strlen(reflog
);
359 if (state
->reflog_width
< id_width
) {
360 struct view_column
*column
= get_view_column(view
, VIEW_COLUMN_ID
);
362 state
->reflog_width
= id_width
;
363 if (column
&& column
->opt
.id
.display
)
364 view
->force_redraw
= TRUE
;
370 /* Reads git log --pretty=raw output and parses it into the commit struct. */
372 main_read(struct view
*view
, struct buffer
*buf
)
374 struct main_state
*state
= view
->private;
375 struct graph
*graph
= state
->graph
;
377 struct commit
*commit
= &state
->current
;
381 main_flush_commit(view
, commit
);
383 if (failed_to_load_initial_view(view
))
384 die("No revisions match the given arguments.");
385 if (view
->lines
> 0) {
386 struct commit
*last
= view
->line
[view
->lines
- 1].data
;
388 view
->line
[view
->lines
- 1].dirty
= 1;
396 state
->graph
->done_rendering(graph
);
401 type
= get_line_type(line
);
402 if (type
== LINE_COMMIT
) {
406 state
->in_header
= TRUE
;
407 line
+= STRING_SIZE("commit ");
408 is_boundary
= *line
== '-';
409 while (*line
&& !isalnum(*line
))
412 main_flush_commit(view
, commit
);
414 author
= io_memchr(buf
, line
, 0);
416 if (state
->first_parent
) {
417 char *parent
= strchr(line
, ' ');
418 char *parent_end
= parent
? strchr(parent
+ 1, ' ') : NULL
;
424 main_register_commit(view
, &state
->current
, line
, is_boundary
);
427 char *title
= io_memchr(buf
, author
, 0);
429 parse_author_line(author
, &commit
->author
, &commit
->time
);
430 if (state
->with_graph
)
431 graph
->render_parents(graph
, &commit
->graph
);
433 main_add_commit(view
, LINE_MAIN_COMMIT
, commit
, title
, FALSE
);
442 /* Empty line separates the commit header from the log itself. */
444 state
->in_header
= FALSE
;
448 if (!main_add_reflog(view
, state
, line
+ STRING_SIZE("Reflog: ")))
452 case LINE_PP_REFLOGMSG
:
453 line
+= STRING_SIZE("Reflog message: ");
454 string_ncopy(state
->reflogmsg
, line
, strlen(line
));
458 if (state
->with_graph
)
459 graph
->add_parent(graph
, line
+ STRING_SIZE("parent "));
463 parse_author_line(line
+ STRING_SIZE("author "),
464 &commit
->author
, &commit
->time
);
465 if (state
->with_graph
)
466 graph
->render_parents(graph
, &commit
->graph
);
470 /* Fill in the commit title if it has not already been set. */
474 /* Skip lines in the commit header. */
475 if (state
->in_header
)
478 /* Require titles to start with a non-space character at the
479 * offset used by git log. */
480 if (strncmp(line
, " ", 4))
483 /* Well, if the title starts with a whitespace character,
484 * try to be forgiving. Otherwise we end up with no title. */
485 while (isspace(*line
))
489 if (*state
->reflogmsg
)
490 line
= state
->reflogmsg
;
491 main_add_commit(view
, LINE_MAIN_COMMIT
, commit
, line
, FALSE
);
498 main_request(struct view
*view
, enum request request
, struct line
*line
)
500 enum open_flags flags
= (view_is_displayed(view
) && request
!= REQ_VIEW_DIFF
)
501 ? OPEN_SPLIT
: OPEN_DEFAULT
;
506 if (view_is_displayed(view
) && display
[0] != view
)
508 /* Do not pass navigation requests to the branch view
509 * when the main view is maximized. (GH #38) */
510 return request
== REQ_NEXT
? REQ_MOVE_DOWN
: REQ_MOVE_UP
;
514 if (view_is_displayed(view
) && display
[0] != view
)
515 maximize_view(view
, TRUE
);
517 if (line
->type
== LINE_STAT_UNSTAGED
518 || line
->type
== LINE_STAT_STAGED
)
519 open_stage_view(view
, NULL
, line
->type
, flags
);
521 open_diff_view(view
, flags
);
536 /* Update the env from last ref to first using recursion. */
538 main_update_env(struct view
*view
, const struct ref
*ref
)
541 main_update_env(view
, ref
->next
);
542 ref_update_env(view
->env
, ref
, !ref
->next
);
546 main_select(struct view
*view
, struct line
*line
)
548 struct commit
*commit
= line
->data
;
550 if (line
->type
== LINE_STAT_STAGED
|| line
->type
== LINE_STAT_UNSTAGED
) {
551 string_ncopy(view
->ref
, commit
->title
, strlen(commit
->title
));
552 status_stage_info(view
->env
->status
, line
->type
, NULL
);
554 const struct ref
*ref
= main_get_commit_refs(line
, commit
);
556 string_copy_rev(view
->ref
, commit
->id
);
558 main_update_env(view
, ref
);
560 string_copy_rev(view
->env
->commit
, commit
->id
);
563 static struct view_ops main_ops
= {
566 VIEW_SEND_CHILD_ENTER
| VIEW_FILE_FILTER
| VIEW_LOG_LIKE
| VIEW_REFRESH
,
567 sizeof(struct main_state
),
575 view_column_bit(AUTHOR
) | view_column_bit(COMMIT_TITLE
) |
576 view_column_bit(DATE
) | view_column_bit(ID
) |
577 view_column_bit(LINE_NUMBER
),
578 main_get_column_data
,
583 /* vim: set ts=8 sw=8 noexpandtab: */