index 6ad9c57953182e4d54d4ad3fdca66d61e8dfc4b3..e58a6b65f2f3e3c5e50f15c7c275f569fbdd11aa 100644 (file)
--- a/tig.c
+++ b/tig.c
#include "tig.h"
#include "io.h"
+#include "graph.h"
static void __NORETURN die(const char *err, ...);
static void warn(const char *msg, ...);
static bool prompt_menu(const char *prompt, const struct menu_item *items, int *selected);
+enum graphic {
+ GRAPHIC_ASCII = 0,
+ GRAPHIC_DEFAULT,
+ GRAPHIC_UTF8
+};
+
+static const struct enum_map graphic_map[] = {
+#define GRAPHIC_(name) ENUM_MAP(#name, GRAPHIC_##name)
+ GRAPHIC_(ASCII),
+ GRAPHIC_(DEFAULT),
+ GRAPHIC_(UTF8)
+#undef GRAPHIC_
+};
+
#define DATE_INFO \
DATE_(NO), \
DATE_(DEFAULT), \
REQ_(TOGGLE_DATE, "Toggle date display"), \
REQ_(TOGGLE_AUTHOR, "Toggle author display"), \
REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
+ REQ_(TOGGLE_GRAPHIC, "Toggle (line) graphics mode"), \
REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
REQ_(TOGGLE_SORT_ORDER, "Toggle ascending/descending sort order"), \
REQ_(TOGGLE_SORT_FIELD, "Toggle field to sort by"), \
*/
/* Option and state variables. */
+static enum graphic opt_line_graphics = GRAPHIC_DEFAULT;
static enum date opt_date = DATE_DEFAULT;
static enum author opt_author = AUTHOR_DEFAULT;
+static bool opt_rev_graph = TRUE;
static bool opt_line_number = FALSE;
-static bool opt_line_graphics = TRUE;
-static bool opt_rev_graph = FALSE;
static bool opt_show_refs = TRUE;
static bool opt_untracked_dirs_content = TRUE;
static int opt_num_interval = 5;
static const char **opt_diff_argv = NULL;
static const char **opt_rev_argv = NULL;
static const char **opt_file_argv = NULL;
+static const char **opt_blame_argv = NULL;
#define is_initial_commit() (!get_ref_head())
#define is_head_commit(rev) (!strcmp((rev), "HEAD") || (get_ref_head() && !strcmp(rev, get_ref_head()->id)))
LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(HELP_KEYMAP, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(HELP_GROUP, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
-LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0)
+LINE(BLAME_ID, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_0, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_1, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_2, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_3, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_4, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_5, "", COLOR_WHITE, COLOR_DEFAULT, 0), \
+LINE(GRAPH_LINE_6, "", COLOR_RED, COLOR_DEFAULT, 0), \
+LINE(GRAPH_COMMIT, "", COLOR_BLUE, COLOR_DEFAULT, 0)
enum line_type {
#define LINE(type, line, fg, bg, attr) \
{ 'D', REQ_TOGGLE_DATE },
{ 'A', REQ_TOGGLE_AUTHOR },
{ 'g', REQ_TOGGLE_REV_GRAPH },
+ { '~', REQ_TOGGLE_GRAPHIC },
{ 'F', REQ_TOGGLE_REFS },
{ 'I', REQ_TOGGLE_SORT_ORDER },
{ 'i', REQ_TOGGLE_SORT_FIELD },
OPT_ERR_(NO_OPTION_VALUE, "No option value"), \
OPT_ERR_(NO_VALUE_ASSIGNED, "No value assigned"), \
OPT_ERR_(OBSOLETE_REQUEST_NAME, "Obsolete request name"), \
+ OPT_ERR_(OUT_OF_MEMORY, "Out of memory"), \
OPT_ERR_(TOO_MANY_OPTION_ARGUMENTS, "Too many option arguments"), \
OPT_ERR_(UNKNOWN_ATTRIBUTE, "Unknown attribute"), \
OPT_ERR_(UNKNOWN_COLOR, "Unknown color"), \
}
}
+static enum option_code
+parse_args(const char ***args, const char *argv[])
+{
+ if (*args == NULL && !argv_copy(args, argv))
+ return OPT_ERR_OUT_OF_MEMORY;
+ return OPT_OK;
+}
+
/* Wants: name = value */
static enum option_code
option_set_command(int argc, const char *argv[])
{
- if (argc != 3)
+ if (argc < 3)
return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
if (strcmp(argv[1], "="))
return OPT_ERR_NO_VALUE_ASSIGNED;
+ if (!strcmp(argv[0], "blame-options"))
+ return parse_args(&opt_blame_argv, argv + 2);
+
+ if (argc != 3)
+ return OPT_ERR_WRONG_NUMBER_OF_ARGUMENTS;
+
if (!strcmp(argv[0], "show-author"))
return parse_enum(&opt_author, argv[2], author_map);
return parse_bool(&opt_line_number, argv[2]);
if (!strcmp(argv[0], "line-graphics"))
- return parse_bool(&opt_line_graphics, argv[2]);
+ return parse_enum(&opt_line_graphics, argv[2], graphic_map);
if (!strcmp(argv[0], "line-number-interval"))
return parse_int(&opt_num_interval, argv[2], 1, 1024);
struct view {
enum view_type type; /* View type */
const char *name; /* View name */
- const char *cmd_env; /* Command line set via environment */
const char *id; /* Points to either of ref_{head,commit,blob} */
struct view_ops *ops; /* View operations */
time_t update_secs;
};
+enum open_flags {
+ OPEN_DEFAULT = 0, /* Use default view switching. */
+ OPEN_SPLIT = 1, /* Split current view. */
+ OPEN_RELOAD = 4, /* Reload view even if it is the current. */
+ OPEN_REFRESH = 16, /* Refresh view using previous command. */
+ OPEN_PREPARED = 32, /* Open already prepared command. */
+};
+
struct view_ops {
/* What type of content being displayed. Used in the title bar. */
const char *type;
- /* Default command arguments. */
- const char **argv;
/* Open and reads in all view content. */
- bool (*open)(struct view *view);
+ bool (*open)(struct view *view, enum open_flags flags);
/* Read one line; updates view->line. */
bool (*read)(struct view *view, char *data);
/* Draw one line; @lineno must be < view->height. */
bool (*grep)(struct view *view, struct line *line);
/* Select line */
void (*select)(struct view *view, struct line *line);
- /* Prepare view for loading */
- bool (*prepare)(struct view *view);
};
static struct view_ops blame_ops;
static struct view_ops tree_ops;
static struct view_ops branch_ops;
-#define VIEW_STR(type, name, env, ref, ops, map, git) \
- { type, name, #env, ref, ops, map, git }
+#define VIEW_STR(type, name, ref, ops, map, git) \
+ { type, name, ref, ops, map, git }
#define VIEW_(id, name, ops, git, ref) \
- VIEW_STR(VIEW_##id, name, TIG_##id##_CMD, ref, ops, KEYMAP_##id, git)
+ VIEW_STR(VIEW_##id, name, ref, ops, KEYMAP_##id, git)
static struct view views[] = {
VIEW_(MAIN, "main", &main_ops, TRUE, ref_head),
}
}
-static int
+#define VIEW_MAX_LEN(view) ((view)->width + (view)->yoffset - (view)->col)
+
+static bool
draw_chars(struct view *view, enum line_type type, const char *string,
int max_len, bool use_tilde)
{
size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
if (max_len <= 0)
- return 0;
+ return VIEW_MAX_LEN(view) <= 0;
len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde, opt_tab_size);
}
}
- return col;
+ view->col += col;
+ return VIEW_MAX_LEN(view) <= 0;
}
-static int
+static bool
draw_space(struct view *view, enum line_type type, int max, int spaces)
{
static char space[] = " ";
- int col = 0;
spaces = MIN(max, spaces);
while (spaces > 0) {
int len = MIN(spaces, sizeof(space) - 1);
- col += draw_chars(view, type, space, len, FALSE);
+ if (draw_chars(view, type, space, len, FALSE))
+ return TRUE;
spaces -= len;
}
- return col;
+ return VIEW_MAX_LEN(view) <= 0;
}
static bool
do {
size_t pos = string_expand(text, sizeof(text), string, opt_tab_size);
- view->col += draw_chars(view, type, text, view->width + view->yoffset - view->col, TRUE);
+ if (draw_chars(view, type, text, VIEW_MAX_LEN(view), TRUE))
+ return TRUE;
string += pos;
- } while (*string && view->width + view->yoffset > view->col);
+ } while (*string);
- return view->width + view->yoffset <= view->col;
+ return VIEW_MAX_LEN(view) <= 0;
}
static bool
-draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
+draw_graphic(struct view *view, enum line_type type, const chtype graphic[], size_t size, bool separator)
{
size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
- int max = view->width + view->yoffset - view->col;
+ int max = VIEW_MAX_LEN(view);
int i;
if (max < size)
@@ -1568,27 +1616,26 @@ draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t si
waddch(view->win, graphic[i]);
view->col += size;
- if (size < max && skip <= size)
- waddch(view->win, ' ');
- view->col++;
+ if (separator) {
+ if (size < max && skip <= size)
+ waddch(view->win, ' ');
+ view->col++;
+ }
- return view->width + view->yoffset <= view->col;
+ return VIEW_MAX_LEN(view) <= 0;
}
static bool
draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
{
- int max = MIN(view->width + view->yoffset - view->col, len);
- int col;
+ int max = MIN(VIEW_MAX_LEN(view), len);
+ int col = view->col;
- if (text)
- col = draw_chars(view, type, text, max - 1, trim);
- else
- col = draw_space(view, type, max - 1, max - 1);
+ if (!text)
+ return draw_space(view, type, max, max);
- view->col += col;
- view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
- return view->width + view->yoffset <= view->col;
+ return draw_chars(view, type, text, max - 1, trim)
+ || draw_space(view, LINE_DEFAULT, max - (view->col - col), max);
}
static bool
{
char number[10];
int digits3 = view->digits < 3 ? 3 : view->digits;
- int max = MIN(view->width + view->yoffset - view->col, digits3);
+ int max = MIN(VIEW_MAX_LEN(view), digits3);
char *text = NULL;
chtype separator = opt_line_graphics ? ACS_VLINE : '|';
text = number;
}
if (text)
- view->col += draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
+ draw_chars(view, LINE_LINE_NUMBER, text, max, TRUE);
else
- view->col += draw_space(view, LINE_LINE_NUMBER, max, digits3);
- return draw_graphic(view, LINE_DEFAULT, &separator, 1);
+ draw_space(view, LINE_LINE_NUMBER, max, digits3);
+ return draw_graphic(view, LINE_DEFAULT, &separator, 1, TRUE);
}
static bool
TOGGLE_(LINENO, '.', "line numbers", &opt_line_number, NULL) \
TOGGLE_(DATE, 'D', "dates", &opt_date, date_map) \
TOGGLE_(AUTHOR, 'A', "author names", &opt_author, author_map) \
+ TOGGLE_(GRAPHIC, '~', "graphics", &opt_line_graphics, graphic_map) \
TOGGLE_(REV_GRAPH, 'g', "revision graph", &opt_rev_graph, NULL) \
TOGGLE_(REFS, 'F', "reference display", &opt_show_refs, NULL)
} vars[] = {
#define FORMAT_VAR(name, value, value_if_empty) \
{ name, STRING_SIZE(name), value, value_if_empty }
- FORMAT_VAR("%(directory)", opt_path, ""),
+ FORMAT_VAR("%(directory)", opt_path, "."),
FORMAT_VAR("%(file)", opt_file, ""),
FORMAT_VAR("%(ref)", opt_ref, "HEAD"),
FORMAT_VAR("%(head)", ref_head, ""),
@@ -2355,6 +2403,11 @@ format_argv(const char ***dst_argv, const char *src_argv[], bool replace, bool f
break;
continue;
+ } else if (!strcmp(arg, "%(blameargs)")) {
+ if (!argv_append_array(dst_argv, opt_blame_argv))
+ break;
+ continue;
+
} else if (!strcmp(arg, "%(revargs)") ||
(first && !strcmp(arg, "%(commit)"))) {
if (!argv_append_array(dst_argv, opt_rev_argv))
}
static bool
-begin_update(struct view *view, bool refresh)
+begin_update(struct view *view, const char *dir, const char **argv, enum open_flags flags)
{
+ bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
+ bool refresh = flags & (OPEN_REFRESH | OPEN_PREPARED);
+
+ if (!reload && !strcmp(view->vid, view->id))
+ return TRUE;
+
if (view->pipe)
end_update(view, TRUE);
if (!refresh) {
- if (view->ops->prepare) {
- if (!view->ops->prepare(view))
- return FALSE;
- } else if (!prepare_io(view, NULL, view->ops->argv, TRUE)) {
+ if (!prepare_io(view, dir, argv, TRUE))
return FALSE;
- }
/* Put the current ref_* value to the view title ref
* member. This is needed by the blob view. Most other
return TRUE;
}
+static bool
+view_open(struct view *view, enum open_flags flags)
+{
+ return begin_update(view, NULL, NULL, flags);
+}
+
static bool
update_view(struct view *view)
{
if (!view->pipe)
return TRUE;
- if (!io_can_read(view->pipe)) {
+ if (!io_can_read(view->pipe, FALSE)) {
if (view->lines == 0 && view_is_displayed(view)) {
time_t secs = time(NULL) - view->start_time;
@@ -2637,14 +2698,6 @@ add_line_format(struct view *view, enum line_type type, const char *fmt, ...)
* View opening
*/
-enum open_flags {
- OPEN_DEFAULT = 0, /* Use default view switching. */
- OPEN_SPLIT = 1, /* Split current view. */
- OPEN_RELOAD = 4, /* Reload view even if it is the current. */
- OPEN_REFRESH = 16, /* Refresh view using previous command. */
- OPEN_PREPARED = 32, /* Open already prepared command. */
-};
-
static void
open_view(struct view *prev, enum request request, enum open_flags flags)
{
if (view->ops->open) {
if (view->pipe)
end_update(view, TRUE);
- if (!view->ops->open(view)) {
+ if (!view->ops->open(view, flags)) {
report("Failed to load %s view", view->name);
return;
}
restore_view_position(view);
-
- } else if ((reload || strcmp(view->vid, view->id)) &&
- !begin_update(view, flags & (OPEN_REFRESH | OPEN_PREPARED))) {
- report("Failed to load %s view", view->name);
- return;
}
if (split && prev->lineno - prev->offset >= prev->height) {
case REQ_TOGGLE_LINENO:
case REQ_TOGGLE_DATE:
case REQ_TOGGLE_AUTHOR:
+ case REQ_TOGGLE_GRAPHIC:
case REQ_TOGGLE_REV_GRAPH:
case REQ_TOGGLE_REFS:
toggle_option(request);
static struct view_ops pager_ops = {
"line",
- NULL,
- NULL,
+ view_open,
pager_read,
pager_draw,
pager_request,
pager_select,
};
-static const char *log_argv[SIZEOF_ARG] = {
- "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
-};
+static bool
+log_open(struct view *view, enum open_flags flags)
+{
+ static const char *log_argv[] = {
+ "git", "log", "--no-color", "--cc", "--stat", "-n100", "%(head)", NULL
+ };
+
+ return begin_update(view, NULL, log_argv, flags);
+}
static enum request
log_request(struct view *view, enum request request, struct line *line)
static struct view_ops log_ops = {
"line",
- log_argv,
- NULL,
+ log_open,
pager_read,
pager_draw,
log_request,
pager_select,
};
-static const char *diff_argv[SIZEOF_ARG] = {
- "git", "show", "--pretty=fuller", "--no-color", "--root",
- "--patch-with-stat", "--find-copies-harder", "-C",
- "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
-};
+static bool
+diff_open(struct view *view, enum open_flags flags)
+{
+ static const char *diff_argv[] = {
+ "git", "show", "--pretty=fuller", "--no-color", "--root",
+ "--patch-with-stat", "--find-copies-harder", "-C",
+ "%(diffargs)", "%(commit)", "--", "%(fileargs)", NULL
+ };
+
+ return begin_update(view, NULL, diff_argv, flags);
+}
static bool
diff_read(struct view *view, char *data)
static struct view_ops diff_ops = {
"line",
- diff_argv,
- NULL,
+ diff_open,
diff_read,
pager_draw,
pager_request,
}
static bool
-help_open(struct view *view)
+help_open(struct view *view, enum open_flags flags)
{
enum keymap keymap;
static struct view_ops help_ops = {
"line",
- NULL,
help_open,
NULL,
pager_draw,
}
static bool
-tree_prepare(struct view *view)
+tree_open(struct view *view, enum open_flags flags)
{
+ static const char *tree_argv[] = {
+ "git", "ls-tree", "%(commit)", "%(directory)", NULL
+ };
+
if (view->lines == 0 && opt_prefix[0]) {
char *pos = opt_prefix;
opt_path[0] = 0;
}
- return prepare_io(view, opt_cdup, view->ops->argv, TRUE);
+ return begin_update(view, opt_cdup, tree_argv, flags);
}
-static const char *tree_argv[SIZEOF_ARG] = {
- "git", "ls-tree", "%(commit)", "%(directory)", NULL
-};
-
static struct view_ops tree_ops = {
"file",
- tree_argv,
- NULL,
+ tree_open,
tree_read,
tree_draw,
tree_request,
tree_grep,
tree_select,
- tree_prepare,
};
+static bool
+blob_open(struct view *view, enum open_flags flags)
+{
+ static const char *blob_argv[] = {
+ "git", "cat-file", "blob", "%(blob)", NULL
+ };
+
+ return begin_update(view, NULL, blob_argv, flags);
+}
+
static bool
blob_read(struct view *view, char *line)
{
}
}
-static const char *blob_argv[SIZEOF_ARG] = {
- "git", "cat-file", "blob", "%(blob)", NULL
-};
-
static struct view_ops blob_ops = {
"line",
- blob_argv,
- NULL,
+ blob_open,
blob_read,
pager_draw,
blob_request,
};
static bool
-blame_open(struct view *view)
+blame_open(struct view *view, enum open_flags flags)
{
char path[SIZEOF_STR];
size_t i;
{
if (!line) {
const char *blame_argv[] = {
- "git", "blame", "--incremental",
+ "git", "blame", "%(blameargs)", "--incremental",
*opt_ref ? opt_ref : "--incremental", "--", opt_file, NULL
};
static struct view_ops blame_ops = {
"line",
- NULL,
blame_open,
blame_read,
blame_draw,
}
static bool
-branch_open(struct view *view)
+branch_open(struct view *view, enum open_flags flags)
{
const char *branch_log[] = {
"git", "log", "--no-color", "--pretty=raw",
static struct view_ops branch_ops = {
"branch",
- NULL,
branch_open,
branch_read,
branch_draw,
* info using git-diff-files(1), and finally untracked files using
* git-ls-files(1). */
static bool
-status_open(struct view *view)
+status_open(struct view *view, enum open_flags flags)
{
reset_view(view);
static struct view_ops status_ops = {
"file",
- NULL,
status_open,
NULL,
status_draw,
static struct view_ops stage_ops = {
"line",
- NULL,
- NULL,
+ view_open,
pager_read,
pager_draw,
stage_request,
* Revision graph
*/
-struct commit {
- char id[SIZEOF_REV]; /* SHA1 ID. */
- char title[128]; /* First line of the commit message. */
- const char *author; /* Author of the commit. */
- struct time time; /* Date from the author ident. */
- struct ref_list *refs; /* Repository references. */
- chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
- size_t graph_size; /* The width of the graph array. */
- bool has_parents; /* Rewritten --parents seen. */
-};
-
-/* Size of rev graph with no "padding" columns */
-#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
-
-struct rev_graph {
- struct rev_graph *prev, *next, *parents;
- char rev[SIZEOF_REVITEMS][SIZEOF_REV];
- size_t size;
- struct commit *commit;
- size_t pos;
- unsigned int boundary:1;
-};
-
-/* Parents of the commit being visualized. */
-static struct rev_graph graph_parents[4];
-
-/* The current stack of revisions on the graph. */
-static struct rev_graph graph_stacks[4] = {
- { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
- { &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
- { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
- { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
+static const enum line_type graph_colors[] = {
+ LINE_GRAPH_LINE_0,
+ LINE_GRAPH_LINE_1,
+ LINE_GRAPH_LINE_2,
+ LINE_GRAPH_LINE_3,
+ LINE_GRAPH_LINE_4,
+ LINE_GRAPH_LINE_5,
+ LINE_GRAPH_LINE_6,
};
-static inline bool
-graph_parent_is_merge(struct rev_graph *graph)
+static enum line_type get_graph_color(struct graph_symbol *symbol)
{
- return graph->parents->size > 1;
+ if (symbol->commit)
+ return LINE_GRAPH_COMMIT;
+ assert(symbol->color < ARRAY_SIZE(graph_colors));
+ return graph_colors[symbol->color];
}
-static inline void
-append_to_rev_graph(struct rev_graph *graph, chtype symbol)
+static bool
+draw_graph_utf8(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
{
- struct commit *commit = graph->commit;
-
- if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
- commit->graph[commit->graph_size++] = symbol;
-}
+ const char *chars = graph_symbol_to_utf8(symbol);
-static void
-clear_rev_graph(struct rev_graph *graph)
-{
- graph->boundary = 0;
- graph->size = graph->pos = 0;
- graph->commit = NULL;
- memset(graph->parents, 0, sizeof(*graph->parents));
+ return draw_text(view, color, chars + !!first);
}
-static void
-done_rev_graph(struct rev_graph *graph)
+static bool
+draw_graph_ascii(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
{
- if (graph_parent_is_merge(graph) &&
- graph->pos < graph->size - 1 &&
- graph->next->size == graph->size + graph->parents->size - 1) {
- size_t i = graph->pos + graph->parents->size - 1;
-
- graph->commit->graph_size = i * 2;
- while (i < graph->next->size - 1) {
- append_to_rev_graph(graph, ' ');
- append_to_rev_graph(graph, '\\');
- i++;
- }
- }
+ const char *chars = graph_symbol_to_ascii(symbol);
- clear_rev_graph(graph);
+ return draw_text(view, color, chars + !!first);
}
-static void
-push_rev_graph(struct rev_graph *graph, const char *parent)
+static bool
+draw_graph_chtype(struct view *view, struct graph_symbol *symbol, enum line_type color, bool first)
{
- int i;
+ const chtype *chars = graph_symbol_to_chtype(symbol);
- /* "Collapse" duplicate parents lines.
- *
- * FIXME: This needs to also update update the drawn graph but
- * for now it just serves as a method for pruning graph lines. */
- for (i = 0; i < graph->size; i++)
- if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
- return;
-
- if (graph->size < SIZEOF_REVITEMS) {
- string_copy_rev(graph->rev[graph->size++], parent);
- }
+ return draw_graphic(view, color, chars + !!first, 2 - !!first, FALSE);
}
-static chtype
-get_rev_graph_symbol(struct rev_graph *graph)
-{
- chtype symbol;
-
- if (graph->boundary)
- symbol = REVGRAPH_BOUND;
- else if (graph->parents->size == 0)
- symbol = REVGRAPH_INIT;
- else if (graph_parent_is_merge(graph))
- symbol = REVGRAPH_MERGE;
- else if (graph->pos >= graph->size)
- symbol = REVGRAPH_BRANCH;
- else
- symbol = REVGRAPH_COMMIT;
-
- return symbol;
-}
+typedef bool (*draw_graph_fn)(struct view *, struct graph_symbol *, enum line_type, bool);
-static void
-draw_rev_graph(struct rev_graph *graph)
+static bool draw_graph(struct view *view, struct graph_canvas *canvas)
{
- struct rev_filler {
- chtype separator, line;
+ static const draw_graph_fn fns[] = {
+ draw_graph_ascii,
+ draw_graph_chtype,
+ draw_graph_utf8
};
- enum { DEFAULT, RSHARP, RDIAG, LDIAG };
- static struct rev_filler fillers[] = {
- { ' ', '|' },
- { '`', '.' },
- { '\'', ' ' },
- { '/', ' ' },
- };
- chtype symbol = get_rev_graph_symbol(graph);
- struct rev_filler *filler;
- size_t i;
-
- fillers[DEFAULT].line = opt_line_graphics ? ACS_VLINE : '|';
- filler = &fillers[DEFAULT];
-
- for (i = 0; i < graph->pos; i++) {
- append_to_rev_graph(graph, filler->line);
- if (graph_parent_is_merge(graph->prev) &&
- graph->prev->pos == i)
- filler = &fillers[RSHARP];
-
- append_to_rev_graph(graph, filler->separator);
- }
-
- /* Place the symbol for this revision. */
- append_to_rev_graph(graph, symbol);
-
- if (graph->prev->size > graph->size)
- filler = &fillers[RDIAG];
- else
- filler = &fillers[DEFAULT];
-
- i++;
-
- for (; i < graph->size; i++) {
- append_to_rev_graph(graph, filler->separator);
- append_to_rev_graph(graph, filler->line);
- if (graph_parent_is_merge(graph->prev) &&
- i < graph->prev->pos + graph->parents->size)
- filler = &fillers[RSHARP];
- if (graph->prev->size > graph->size)
- filler = &fillers[LDIAG];
- }
-
- if (graph->prev->size > graph->size) {
- append_to_rev_graph(graph, filler->separator);
- if (filler->line != ' ')
- append_to_rev_graph(graph, filler->line);
- }
-}
-
-/* Prepare the next rev graph */
-static void
-prepare_rev_graph(struct rev_graph *graph)
-{
- size_t i;
+ draw_graph_fn fn = fns[opt_line_graphics];
+ int i;
- /* First, traverse all lines of revisions up to the active one. */
- for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
- if (!strcmp(graph->rev[graph->pos], graph->commit->id))
- break;
+ for (i = 0; i < canvas->size; i++) {
+ struct graph_symbol *symbol = &canvas->symbols[i];
+ enum line_type color = get_graph_color(symbol);
- push_rev_graph(graph->next, graph->rev[graph->pos]);
+ if (fn(view, symbol, color, i == 0))
+ return TRUE;
}
- /* Interleave the new revision parent(s). */
- for (i = 0; !graph->boundary && i < graph->parents->size; i++)
- push_rev_graph(graph->next, graph->parents->rev[i]);
-
- /* Lastly, put any remaining revisions. */
- for (i = graph->pos + 1; i < graph->size; i++)
- push_rev_graph(graph->next, graph->rev[i]);
-}
-
-static void
-update_rev_graph(struct view *view, struct rev_graph *graph)
-{
- /* If this is the finalizing update ... */
- if (graph->commit)
- prepare_rev_graph(graph);
-
- /* Graph visualization needs a one rev look-ahead,
- * so the first update doesn't visualize anything. */
- if (!graph->prev->commit)
- return;
-
- if (view->lines > 2)
- view->line[view->lines - 3].dirty = 1;
- if (view->lines > 1)
- view->line[view->lines - 2].dirty = 1;
- draw_rev_graph(graph->prev);
- done_rev_graph(graph->prev->prev);
+ return draw_text(view, LINE_MAIN_REVGRAPH, " ");
}
-
/*
* Main view backend
*/
-static const char *main_argv[SIZEOF_ARG] = {
- "git", "log", "--no-color", "--pretty=raw", "--parents",
- "--topo-order", "%(diffargs)", "%(revargs)",
- "--", "%(fileargs)", NULL
+struct commit {
+ char id[SIZEOF_REV]; /* SHA1 ID. */
+ char title[128]; /* First line of the commit message. */
+ const char *author; /* Author of the commit. */
+ struct time time; /* Date from the author ident. */
+ struct ref_list *refs; /* Repository references. */
+ struct graph_canvas graph; /* Ancestry chain graphics. */
};
+static bool
+main_open(struct view *view, enum open_flags flags)
+{
+ static const char *main_argv[] = {
+ "git", "log", "--no-color", "--pretty=raw", "--parents",
+ "--topo-order", "%(diffargs)", "%(revargs)",
+ "--", "%(fileargs)", NULL
+ };
+
+ return begin_update(view, NULL, main_argv, flags);
+}
+
static bool
main_draw(struct view *view, struct line *line, unsigned int lineno)
{
if (opt_author && draw_author(view, commit->author))
return TRUE;
- if (opt_rev_graph && commit->graph_size &&
- draw_graphic(view, LINE_MAIN_REVGRAPH, commit->graph, commit->graph_size))
+ if (opt_rev_graph && draw_graph(view, &commit->graph))
return TRUE;
if (opt_show_refs && commit->refs) {
static bool
main_read(struct view *view, char *line)
{
- static struct rev_graph *graph = graph_stacks;
+ static struct graph graph;
enum line_type type;
struct commit *commit;
if (!line) {
- int i;
-
if (!view->lines && !view->prev)
die("No revisions match the given arguments.");
if (view->lines > 0) {
if (!commit->author) {
view->lines--;
free(commit);
- graph->commit = NULL;
}
}
- update_rev_graph(view, graph);
- for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
- clear_rev_graph(&graph_stacks[i]);
+ done_graph(&graph);
return TRUE;
}
type = get_line_type(line);
if (type == LINE_COMMIT) {
+ bool is_boundary;
+
commit = calloc(1, sizeof(struct commit));
if (!commit)
return FALSE;
line += STRING_SIZE("commit ");
- if (*line == '-') {
- graph->boundary = 1;
+ is_boundary = *line == '-';
+ if (is_boundary)
line++;
- }
string_copy_rev(commit->id, line);
commit->refs = get_ref_list(commit->id);
- graph->commit = commit;
add_line_data(view, commit, LINE_MAIN_COMMIT);
-
- while ((line = strchr(line, ' '))) {
- line++;
- push_rev_graph(graph->parents, line);
- commit->has_parents = TRUE;
- }
+ graph_add_commit(&graph, &commit->graph, commit->id, line, is_boundary);
return TRUE;
}
switch (type) {
case LINE_PARENT:
- if (commit->has_parents)
- break;
- push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
+ if (!graph.has_parents)
+ graph_add_parent(&graph, line + STRING_SIZE("parent "));
break;
case LINE_AUTHOR:
parse_author_line(line + STRING_SIZE("author "),
&commit->author, &commit->time);
- update_rev_graph(view, graph);
- graph = graph->next;
+ graph_render_parents(&graph);
break;
default:
static struct view_ops main_ops = {
"commit",
- main_argv,
- NULL,
+ main_open,
main_read,
main_draw,
main_request,
"\n"
"Usage: tig [options] [revs] [--] [paths]\n"
" or: tig show [options] [revs] [--] [paths]\n"
-" or: tig blame [rev] path\n"
+" or: tig blame [options] [rev] [--] path\n"
" or: tig status\n"
" or: tig < [git command output]\n"
"\n"
return REQ_VIEW_STATUS;
} else if (!strcmp(subcommand, "blame")) {
- if (argc <= 2 || argc > 4)
+ filter_rev_parse(&opt_file_argv, "--no-revs", "--no-flags", argv + 2);
+ filter_rev_parse(&opt_blame_argv, "--no-revs", "--flags", argv + 2);
+ filter_rev_parse(&opt_rev_argv, "--symbolic", "--revs-only", argv + 2);
+
+ if (!opt_file_argv || opt_file_argv[1] || (opt_rev_argv && opt_rev_argv[1]))
die("invalid number of options to blame\n\n%s", usage);
- i = 2;
- if (argc == 4) {
- string_ncopy(opt_ref, argv[i], strlen(argv[i]));
- i++;
+ if (opt_rev_argv) {
+ string_ncopy(opt_ref, opt_rev_argv[0], strlen(opt_rev_argv[0]));
}
- string_ncopy(opt_file, argv[i], strlen(argv[i]));
+ string_ncopy(opt_file, opt_file_argv[0], strlen(opt_file_argv[0]));
return REQ_VIEW_BLAME;
} else if (!strcmp(subcommand, "show")) {
const char *codeset = "UTF-8";
enum request request = parse_options(argc, argv);
struct view *view;
- size_t i;
signal(SIGINT, quit);
signal(SIGPIPE, SIG_IGN);
if (load_refs() == ERR)
die("Failed to load refs.");
- foreach_view (view, i) {
- if (getenv(view->cmd_env))
- warn("Use of the %s environment variable is deprecated,"
- " use options or TIG_DIFF_ARGS instead",
- view->cmd_env);
- if (!argv_from_env(view->ops->argv, view->cmd_env))
- die("Too many arguments in the `%s` environment variable",
- view->cmd_env);
- }
-
init_display();
while (view_driver(display[current_view], request)) {