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.
15 #include "tig/options.h"
16 #include "tig/parse.h"
17 #include "tig/graph.h"
18 #include "tig/display.h"
22 #include "tig/status.h"
30 DEFINE_ALLOCATOR(realloc_reflogs
, char *, 32)
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];
52 /* FIXME: More graceful handling of titles; append "..." to
53 * shortened titles, etc. */
54 string_expand(buf
, sizeof(buf
), title
, 1);
56 titlelen
= strlen(title
);
58 line
= add_line_alloc(view
, &commit
, type
, titlelen
, custom
);
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
);
73 main_flush_commit(struct view
*view
, struct commit
*commit
)
76 main_add_commit(view
, LINE_MAIN_COMMIT
, commit
, "", FALSE
);
80 main_has_changes(const char *argv
[])
84 if (!io_exec(&io
, IO_BG
, NULL
, opt_env
, argv
, -1))
87 return io
.status
== 1;
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
= {};
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
);
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
))
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");
144 main_check_argv(struct view
*view
, const char *argv
[])
146 struct main_state
*state
= view
->private;
147 bool with_reflog
= FALSE
;
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
);
158 column
->opt
.commit_title
.graph
= TRUE
;
159 if (opt_commit_order
!= COMMIT_ORDER_REVERSE
)
160 state
->with_graph
= TRUE
;
166 if (!argv_parse_rev_flag(arg
, &rev_flags
))
169 if (rev_flags
.with_reflog
)
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
));
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
);
210 main_done(struct view
*view
)
212 struct main_state
*state
= view
->private;
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
]);
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
);
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
;
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
;
264 main_add_reflog(struct view
*view
, struct main_state
*state
, char *reflog
)
266 char *end
= strchr(reflog
, ' ');
273 if (!realloc_reflogs(&state
->reflog
, state
->reflogs
, 1)
274 || !(reflog
= strdup(reflog
)))
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
;
290 /* Reads git log --pretty=raw output and parses it into the commit struct. */
292 main_read(struct view
*view
, char *line
)
294 struct main_state
*state
= view
->private;
295 struct graph
*graph
= &state
->graph
;
297 struct commit
*commit
= &state
->current
;
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;
314 if (state
->with_graph
)
319 type
= get_line_type(line
);
320 if (type
== LINE_COMMIT
) {
324 state
->in_header
= TRUE
;
325 line
+= STRING_SIZE("commit ");
326 is_boundary
= *line
== '-';
327 while (*line
&& !isalnum(*line
))
330 if (!state
->added_changes_commits
&& opt_show_changes
&& repo
.is_inside_work_tree
)
331 main_add_changes_commits(view
, state
, line
);
333 main_flush_commit(view
, commit
);
335 main_register_commit(view
, &state
->current
, line
, is_boundary
);
337 author
= io_memchr(&view
->io
, line
, 0);
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
);
345 main_add_commit(view
, LINE_MAIN_COMMIT
, commit
, title
, FALSE
);
354 /* Empty line separates the commit header from the log itself. */
356 state
->in_header
= FALSE
;
360 if (!main_add_reflog(view
, state
, line
+ STRING_SIZE("Reflog: ")))
364 case LINE_PP_REFLOGMSG
:
365 line
+= STRING_SIZE("Reflog message: ");
366 string_ncopy(state
->reflogmsg
, line
, strlen(line
));
370 if (state
->with_graph
&& !graph
->has_parents
)
371 graph_add_parent(graph
, line
+ STRING_SIZE("parent "));
375 parse_author_line(line
+ STRING_SIZE("author "),
376 &commit
->author
, &commit
->time
);
377 if (state
->with_graph
)
378 graph_render_parents(graph
);
382 /* Fill in the commit title if it has not already been set. */
386 /* Skip lines in the commit header. */
387 if (state
->in_header
)
390 /* Require titles to start with a non-space character at the
391 * offset used by git log. */
392 if (strncmp(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
))
401 if (*state
->reflogmsg
)
402 line
= state
->reflogmsg
;
403 main_add_commit(view
, LINE_MAIN_COMMIT
, commit
, line
, FALSE
);
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
;
418 if (view_is_displayed(view
) && display
[0] != view
)
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
;
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
,
435 ignore_space_arg(), NULL
, NULL
)
437 const char *diff_unstaged_argv
[] = {
438 GIT_DIFF_UNSTAGED(encoding_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
);
449 open_diff_view(view
, flags
);
457 case REQ_JUMP_COMMIT
:
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
);
471 report("Unable to find commit '%s'", view
->env
->search
);
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
;
488 for (i
= 0; list
&& i
< list
->size
; i
++) {
489 struct ref
*ref
= list
->refs
[i
];
491 switch (get_line_type_from_ref(ref
)) {
494 /* Always prefer local branches. */
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
));
513 struct ref
*branch
= main_get_commit_branch(line
, commit
);
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
= {
525 VIEW_SEND_CHILD_ENTER
| VIEW_FILE_FILTER
| VIEW_LOG_LIKE
| VIEW_REFRESH
,
526 sizeof(struct main_state
),
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
,
542 /* vim: set ts=8 sw=8 noexpandtab: */