index 095e9c6e92e11dbd41c86e73b5569afdb914cdb4..180661a4fcfbc94067a795898df1a6fc6a6e0330 100644 (file)
--- a/tig.c
+++ b/tig.c
static void warn(const char *msg, ...);
static void report(const char *msg, ...);
static void set_nonblocking_input(bool loading);
-static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
static int load_refs(void);
+static size_t utf8_length(const char **string, size_t col, int *width, size_t max_width, int *trimmed, bool reserve);
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define NULL_ID "0000000000000000000000000000000000000000"
+#define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
+
#ifndef GIT_CONFIG
#define GIT_CONFIG "config"
#endif
-/* Some ascii-shorthands fitted into the ncurses namespace. */
+/* Some ASCII-shorthands fitted into the ncurses namespace. */
#define KEY_TAB '\t'
#define KEY_RETURN '\r'
#define KEY_ESC 27
static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
-struct int_map {
- const char *name;
- int namelen;
- int value;
-};
-
-static int
-set_from_int_map(struct int_map *map, size_t map_size,
- int *value, const char *name, int namelen)
-{
-
- int i;
-
- for (i = 0; i < map_size; i++)
- if (namelen == map[i].namelen &&
- !strncasecmp(name, map[i].name, namelen)) {
- *value = map[i].value;
- return OK;
- }
-
- return ERR;
-}
-
enum input_status {
INPUT_OK,
INPUT_SKIP,
#define string_add(dst, from, src) \
string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
+static void
+string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
+{
+ size_t size, pos;
+
+ for (size = pos = 0; size < dstlen - 1 && src[pos]; pos++) {
+ if (src[pos] == '\t') {
+ size_t expanded = tabsize - (size % tabsize);
+
+ if (expanded + size >= dstlen - 1)
+ expanded = dstlen - size - 1;
+ memcpy(dst + size, " ", expanded);
+ size += expanded;
+ } else {
+ dst[size++] = src[pos];
+ }
+ }
+
+ dst[size] = 0;
+}
+
static char *
chomp_string(char *name)
{
return 0;
}
+struct enum_map {
+ const char *name;
+ int namelen;
+ int value;
+};
+
+#define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
+
+static bool
+map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
+{
+ size_t namelen = strlen(name);
+ int i;
+
+ for (i = 0; i < map_size; i++)
+ if (namelen == map[i].namelen &&
+ !string_enum_compare(name, map[i].name, namelen)) {
+ *value = map[i].value;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+#define map_enum(attr, map, name) \
+ map_enum_do(map, ARRAY_SIZE(map), attr, name)
+
#define prefixcmp(str1, str2) \
strncmp(str1, str2, STRING_SIZE(str2))
cmd[valuelen] = 0;
argv[(*argc)++] = chomp_string(cmd);
- cmd += valuelen + advance;
+ cmd = chomp_string(cmd + valuelen + advance);
}
if (*argc < SIZEOF_ARG)
{
init_io(io, NULL, IO_FD);
io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
+ if (io->pipe == -1)
+ io->error = errno;
return io->pipe != -1;
}
return io->error;
}
-static bool
+static char *
io_strerror(struct io *io)
{
return strerror(io->error);
return written == bufsize;
}
+static bool
+io_read_buf(struct io *io, char buf[], size_t bufsize)
+{
+ bool error;
+
+ io->buf = io->bufpos = buf;
+ io->bufalloc = bufsize;
+ error = !io_get(io, '\n', TRUE) && io_error(io);
+ io->buf = NULL;
+
+ return done_io(io) || error;
+}
+
static bool
run_io_buf(const char **argv, char buf[], size_t bufsize)
{
struct io io = {};
- bool error;
- if (!run_io_rd(&io, argv, FORMAT_NONE))
- return FALSE;
+ return run_io_rd(&io, argv, FORMAT_NONE) && io_read_buf(&io, buf, bufsize);
+}
+
+static int
+io_load(struct io *io, const char *separators,
+ int (*read_property)(char *, size_t, char *, size_t))
+{
+ char *name;
+ int state = OK;
+
+ if (!start_io(io))
+ return ERR;
+
+ while (state == OK && (name = io_get(io, '\n', TRUE))) {
+ char *value;
+ size_t namelen;
+ size_t valuelen;
+
+ name = chomp_string(name);
+ namelen = strcspn(name, separators);
- io.buf = io.bufpos = buf;
- io.bufalloc = bufsize;
- error = !io_get(&io, '\n', TRUE) && io_error(&io);
- io.buf = NULL;
+ if (name[namelen]) {
+ name[namelen] = 0;
+ value = chomp_string(name + namelen + 1);
+ valuelen = strlen(value);
- return done_io(&io) || error;
+ } else {
+ value = "";
+ valuelen = 0;
+ }
+
+ state = read_property(name, namelen, value, valuelen);
+ }
+
+ if (state != ERR && io_error(io))
+ state = ERR;
+ done_io(io);
+
+ return state;
+}
+
+static int
+run_io_load(const char **argv, const char *separators,
+ int (*read_property)(char *, size_t, char *, size_t))
+{
+ struct io io = {};
+
+ return init_io_rd(&io, argv, NULL, FORMAT_NONE)
+ ? io_load(&io, separators, read_property) : ERR;
}
-static int read_properties(struct io *io, const char *separators, int (*read)(char *, size_t, char *, size_t));
/*
* User requests
@@ -698,6 +778,8 @@ static int read_properties(struct io *io, const char *separators, int (*read)(ch
REQ_(MOVE_LAST_LINE, "Move cursor to last line"), \
\
REQ_GROUP("Scrolling") \
+ REQ_(SCROLL_LEFT, "Scroll two columns left"), \
+ REQ_(SCROLL_RIGHT, "Scroll two columns right"), \
REQ_(SCROLL_LINE_UP, "Scroll one line up"), \
REQ_(SCROLL_LINE_DOWN, "Scroll one line down"), \
REQ_(SCROLL_PAGE_UP, "Scroll one page up"), \
const char *help;
};
-static struct request_info req_info[] = {
+static const struct request_info req_info[] = {
#define REQ_GROUP(help) { 0, NULL, 0, (help) },
#define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
REQ_INFO
* Options
*/
-static const char usage[] =
-"tig " TIG_VERSION " (" __DATE__ ")\n"
-"\n"
-"Usage: tig [options] [revs] [--] [paths]\n"
-" or: tig show [options] [revs] [--] [paths]\n"
-" or: tig blame [rev] path\n"
-" or: tig status\n"
-" or: tig < [git command output]\n"
-"\n"
-"Options:\n"
-" -v, --version Show version and exit\n"
-" -h, --help Show help message and exit";
-
/* Option and state variables. */
static bool opt_date = TRUE;
static bool opt_author = TRUE;
static bool opt_rev_graph = FALSE;
static bool opt_show_refs = TRUE;
static int opt_num_interval = NUMBER_INTERVAL;
+static double opt_hscroll = 0.50;
static int opt_tab_size = TAB_SIZE;
static int opt_author_cols = AUTHOR_COLS-1;
static char opt_path[SIZEOF_STR] = "";
#define is_initial_commit() (!*opt_head_rev)
#define is_head_commit(rev) (!strcmp((rev), "HEAD") || !strcmp(opt_head_rev, (rev)))
-static enum request
-parse_options(int argc, const char *argv[], const char ***run_argv)
-{
- enum request request = REQ_VIEW_MAIN;
- const char *subcommand;
- bool seen_dashdash = FALSE;
- /* XXX: This is vulnerable to the user overriding options
- * required for the main view parser. */
- static const char *custom_argv[SIZEOF_ARG] = {
- "git", "log", "--no-color", "--pretty=raw", "--parents",
- "--topo-order", NULL
- };
- int i, j = 6;
-
- if (!isatty(STDIN_FILENO))
- return REQ_VIEW_PAGER;
-
- if (argc <= 1)
- return REQ_VIEW_MAIN;
-
- subcommand = argv[1];
- if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
- if (!strcmp(subcommand, "-S"))
- warn("`-S' has been deprecated; use `tig status' instead");
- if (argc > 2)
- warn("ignoring arguments after `%s'", subcommand);
- return REQ_VIEW_STATUS;
-
- } else if (!strcmp(subcommand, "blame")) {
- if (argc <= 2 || argc > 4)
- 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++;
- }
-
- string_ncopy(opt_file, argv[i], strlen(argv[i]));
- return REQ_VIEW_BLAME;
-
- } else if (!strcmp(subcommand, "show")) {
- request = REQ_VIEW_DIFF;
-
- } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
- request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
- warn("`tig %s' has been deprecated", subcommand);
-
- } else {
- subcommand = NULL;
- }
-
- if (subcommand) {
- custom_argv[1] = subcommand;
- j = 2;
- }
-
- for (i = 1 + !!subcommand; i < argc; i++) {
- const char *opt = argv[i];
-
- if (seen_dashdash || !strcmp(opt, "--")) {
- seen_dashdash = TRUE;
-
- } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
- printf("tig version %s\n", TIG_VERSION);
- return REQ_NONE;
-
- } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
- printf("%s\n", usage);
- return REQ_NONE;
- }
-
- custom_argv[j++] = opt;
- if (j >= ARRAY_SIZE(custom_argv))
- die("command too long");
- }
-
- custom_argv[j] = NULL;
- *run_argv = custom_argv;
-
- return request;
-}
-
/*
* Line-oriented content detection.
LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
-LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(AUTHOR, "author ", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(ACKED, " Acked-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(DELIMITER, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
+LINE(MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(LINE_NUMBER, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
-LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
LINE(MAIN_LOCAL_TAG,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(MAIN_HEAD, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \
LINE(MAIN_REVGRAPH,"", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
-LINE(TREE_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
-LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(TREE_HEAD, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
enum request request;
};
-static struct keybinding default_keybindings[] = {
+static const struct keybinding default_keybindings[] = {
/* View switching */
{ 'm', REQ_VIEW_MAIN },
{ 'd', REQ_VIEW_DIFF },
{ '-', REQ_MOVE_PAGE_UP },
/* Scrolling */
+ { KEY_LEFT, REQ_SCROLL_LEFT },
+ { KEY_RIGHT, REQ_SCROLL_RIGHT },
{ KEY_IC, REQ_SCROLL_LINE_UP },
{ KEY_DC, REQ_SCROLL_LINE_DOWN },
{ 'w', REQ_SCROLL_PAGE_UP },
#undef KEYMAP_
};
-static struct int_map keymap_table[] = {
-#define KEYMAP_(name) { #name, STRING_SIZE(#name), KEYMAP_##name }
+static const struct enum_map keymap_table[] = {
+#define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
KEYMAP_INFO
#undef KEYMAP_
};
-#define set_keymap(map, name) \
- set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
+#define set_keymap(map, name) map_enum(map, keymap_table, name)
struct keybinding_table {
struct keybinding *data;
int value;
};
-static struct key key_table[] = {
+static const struct key key_table[] = {
{ "Enter", KEY_RETURN },
{ "Space", ' ' },
{ "Backspace", KEY_BACKSPACE },
buf[pos] = 0;
for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
- struct keybinding *keybinding = &default_keybindings[i];
+ const struct keybinding *keybinding = &default_keybindings[i];
if (keybinding->request != request)
continue;
* User config file handling.
*/
-static struct int_map color_map[] = {
-#define COLOR_MAP(name) { #name, STRING_SIZE(#name), COLOR_##name }
+static int config_lineno;
+static bool config_errors;
+static const char *config_msg;
+
+static const struct enum_map color_map[] = {
+#define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
COLOR_MAP(DEFAULT),
COLOR_MAP(BLACK),
COLOR_MAP(BLUE),
COLOR_MAP(YELLOW),
};
-#define set_color(color, name) \
- set_from_int_map(color_map, ARRAY_SIZE(color_map), color, name, strlen(name))
-
-static struct int_map attr_map[] = {
-#define ATTR_MAP(name) { #name, STRING_SIZE(#name), A_##name }
+static const struct enum_map attr_map[] = {
+#define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
ATTR_MAP(NORMAL),
ATTR_MAP(BLINK),
ATTR_MAP(BOLD),
ATTR_MAP(UNDERLINE),
};
-#define set_attribute(attr, name) \
- set_from_int_map(attr_map, ARRAY_SIZE(attr_map), attr, name, strlen(name))
+#define set_attribute(attr, name) map_enum(attr, attr_map, name)
-static int config_lineno;
-static bool config_errors;
-static const char *config_msg;
+static int parse_step(double *opt, const char *arg)
+{
+ *opt = atoi(arg);
+ if (!strchr(arg, '%'))
+ return OK;
+
+ /* "Shift down" so 100% and 1 does not conflict. */
+ *opt = (*opt - 1) / 100;
+ if (*opt >= 1.0) {
+ *opt = 0.99;
+ config_msg = "Step value larger than 100%";
+ return ERR;
+ }
+ if (*opt < 0.0) {
+ *opt = 1;
+ config_msg = "Invalid step value";
+ return ERR;
+ }
+ return OK;
+}
-/* Wants: object fgcolor bgcolor [attr] */
+static int
+parse_int(int *opt, const char *arg, int min, int max)
+{
+ int value = atoi(arg);
+
+ if (min <= value && value <= max) {
+ *opt = value;
+ return OK;
+ }
+
+ config_msg = "Integer value out of bound";
+ return ERR;
+}
+
+static bool
+set_color(int *color, const char *name)
+{
+ if (map_enum(color, color_map, name))
+ return TRUE;
+ if (!prefixcmp(name, "color"))
+ return parse_int(color, name + 5, 0, 255) == OK;
+ return FALSE;
+}
+
+/* Wants: object fgcolor bgcolor [attribute] */
static int
option_color_command(int argc, const char *argv[])
{
info = get_line_info(argv[0]);
if (!info) {
- if (!string_enum_compare(argv[0], "main-delim", strlen("main-delim"))) {
- info = get_line_info("delimiter");
-
- } else if (!string_enum_compare(argv[0], "main-date", strlen("main-date"))) {
- info = get_line_info("date");
+ static const struct enum_map obsolete[] = {
+ ENUM_MAP("main-delim", LINE_DELIMITER),
+ ENUM_MAP("main-date", LINE_DATE),
+ ENUM_MAP("main-author", LINE_AUTHOR),
+ };
+ int index;
- } else {
+ if (!map_enum(&index, obsolete, argv[0])) {
config_msg = "Unknown color name";
return ERR;
}
+ info = &line_info[index];
}
- if (set_color(&info->fg, argv[1]) == ERR ||
- set_color(&info->bg, argv[2]) == ERR) {
+ if (!set_color(&info->fg, argv[1]) ||
+ !set_color(&info->bg, argv[2])) {
config_msg = "Unknown color";
return ERR;
}
- if (argc == 4 && set_attribute(&info->attr, argv[3]) == ERR) {
+ if (argc == 4 && !set_attribute(&info->attr, argv[3])) {
config_msg = "Unknown attribute";
return ERR;
}
return OK;
}
-static bool parse_bool(const char *s)
+static int parse_bool(bool *opt, const char *arg)
{
- return (!strcmp(s, "1") || !strcmp(s, "true") ||
- !strcmp(s, "yes")) ? TRUE : FALSE;
+ *opt = (!strcmp(arg, "1") || !strcmp(arg, "true") || !strcmp(arg, "yes"))
+ ? TRUE : FALSE;
+ return OK;
}
static int
-parse_int(const char *s, int default_value, int min, int max)
+parse_string(char *opt, const char *arg, size_t optsize)
{
- int value = atoi(s);
+ int arglen = strlen(arg);
- return (value < min || value > max) ? default_value : value;
+ switch (arg[0]) {
+ case '\"':
+ case '\'':
+ if (arglen == 1 || arg[arglen - 1] != arg[0]) {
+ config_msg = "Unmatched quotation";
+ return ERR;
+ }
+ arg += 1; arglen -= 2;
+ default:
+ string_ncopy_do(opt, optsize, arg, arglen);
+ return OK;
+ }
}
/* Wants: name = value */
return ERR;
}
- if (!strcmp(argv[0], "show-author")) {
- opt_author = parse_bool(argv[2]);
- return OK;
- }
+ if (!strcmp(argv[0], "show-author"))
+ return parse_bool(&opt_author, argv[2]);
- if (!strcmp(argv[0], "show-date")) {
- opt_date = parse_bool(argv[2]);
- return OK;
- }
+ if (!strcmp(argv[0], "show-date"))
+ return parse_bool(&opt_date, argv[2]);
- if (!strcmp(argv[0], "show-rev-graph")) {
- opt_rev_graph = parse_bool(argv[2]);
- return OK;
- }
+ if (!strcmp(argv[0], "show-rev-graph"))
+ return parse_bool(&opt_rev_graph, argv[2]);
- if (!strcmp(argv[0], "show-refs")) {
- opt_show_refs = parse_bool(argv[2]);
- return OK;
- }
+ if (!strcmp(argv[0], "show-refs"))
+ return parse_bool(&opt_show_refs, argv[2]);
- if (!strcmp(argv[0], "show-line-numbers")) {
- opt_line_number = parse_bool(argv[2]);
- return OK;
- }
+ if (!strcmp(argv[0], "show-line-numbers"))
+ return parse_bool(&opt_line_number, argv[2]);
- if (!strcmp(argv[0], "line-graphics")) {
- opt_line_graphics = parse_bool(argv[2]);
- return OK;
- }
+ if (!strcmp(argv[0], "line-graphics"))
+ return parse_bool(&opt_line_graphics, argv[2]);
- if (!strcmp(argv[0], "line-number-interval")) {
- opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
- return OK;
- }
+ if (!strcmp(argv[0], "line-number-interval"))
+ return parse_int(&opt_num_interval, argv[2], 1, 1024);
- if (!strcmp(argv[0], "author-width")) {
- opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
- return OK;
- }
+ if (!strcmp(argv[0], "author-width"))
+ return parse_int(&opt_author_cols, argv[2], 0, 1024);
- if (!strcmp(argv[0], "tab-size")) {
- opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
- return OK;
- }
+ if (!strcmp(argv[0], "horizontal-scroll"))
+ return parse_step(&opt_hscroll, argv[2]);
- if (!strcmp(argv[0], "commit-encoding")) {
- const char *arg = argv[2];
- int arglen = strlen(arg);
+ if (!strcmp(argv[0], "tab-size"))
+ return parse_int(&opt_tab_size, argv[2], 1, 1024);
- switch (arg[0]) {
- case '"':
- case '\'':
- if (arglen == 1 || arg[arglen - 1] != arg[0]) {
- config_msg = "Unmatched quotation";
- return ERR;
- }
- arg += 1; arglen -= 2;
- default:
- string_ncopy(opt_encoding, arg, strlen(arg));
- return OK;
- }
- }
+ if (!strcmp(argv[0], "commit-encoding"))
+ return parse_string(opt_encoding, argv[2], sizeof(opt_encoding));
config_msg = "Unknown variable name";
return ERR;
request = get_request(argv[2]);
if (request == REQ_NONE) {
- struct {
- const char *name;
- enum request request;
- } obsolete[] = {
- { "cherry-pick", REQ_NONE },
- { "screen-resize", REQ_NONE },
- { "tree-parent", REQ_PARENT },
+ static const struct enum_map obsolete[] = {
+ ENUM_MAP("cherry-pick", REQ_NONE),
+ ENUM_MAP("screen-resize", REQ_NONE),
+ ENUM_MAP("tree-parent", REQ_PARENT),
};
- size_t namelen = strlen(argv[2]);
- int i;
+ int alias;
- for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
- if (namelen != strlen(obsolete[i].name) ||
- string_enum_compare(obsolete[i].name, argv[2], namelen))
- continue;
- if (obsolete[i].request != REQ_NONE)
- add_keybinding(keymap, obsolete[i].request, key);
+ if (map_enum(&alias, obsolete, argv[2])) {
+ if (alias != REQ_NONE)
+ add_keybinding(keymap, alias, key);
config_msg = "Obsolete request name";
return ERR;
}
}
if (status == ERR) {
- fprintf(stderr, "Error on line %d, near '%.*s': %s\n",
- config_lineno, (int) optlen, opt, config_msg);
+ warn("Error on line %d, near '%.*s': %s",
+ config_lineno, (int) optlen, opt, config_msg);
config_errors = TRUE;
}
{
struct io io = {};
- /* It's ok that the file doesn't exist. */
+ /* It's OK that the file doesn't exist. */
if (!io_open(&io, path))
return;
config_lineno = 0;
config_errors = FALSE;
- if (read_properties(&io, " \t", read_option) == ERR ||
+ if (io_load(&io, " \t", read_option) == ERR ||
config_errors == TRUE)
- fprintf(stderr, "Errors while loading %s.\n", path);
+ warn("Errors while loading %s.", path);
}
static int
add_builtin_run_requests();
- if (!tigrc_system) {
- if (!string_format(buf, "%s/tigrc", SYSCONFDIR))
- return ERR;
- tigrc_system = buf;
- }
+ if (!tigrc_system)
+ tigrc_system = SYSCONFDIR "/tigrc";
load_option_file(tigrc_system);
if (!tigrc_user) {
static struct view *display[2];
static unsigned int current_view;
-/* Reading from the prompt? */
-static bool input_mode = FALSE;
-
#define foreach_displayed_view(view, i) \
for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
/* Navigation */
unsigned long offset; /* Offset of the window top */
+ unsigned long yoffset; /* Offset from the window side. */
unsigned long lineno; /* Current line number */
unsigned long p_offset; /* Previous offset of the window top */
+ unsigned long p_yoffset;/* Previous offset from the window side */
unsigned long p_lineno; /* Previous current line number */
bool p_restore; /* Should the previous position be restored. */
/* Searching */
char grep[SIZEOF_STR]; /* Search string */
- regex_t *regex; /* Pre-compiled regex */
+ regex_t *regex; /* Pre-compiled regexp */
/* If non-NULL, points to the view that opened this view. If this view
* is closed tig will switch back to the parent view. */
struct line *curline; /* Line currently being drawn. */
enum line_type curtype; /* Attribute currently used for drawing. */
unsigned long col; /* Column when drawing. */
+ bool has_scrolled; /* View was scrolled. */
/* Loading */
struct io io;
bool (*draw)(struct view *view, struct line *line, unsigned int lineno);
/* Depending on view handle a special requests. */
enum request (*request)(struct view *view, enum request request, struct line *line);
- /* Search for regex in a line. */
+ /* Search for regexp in a line. */
bool (*grep)(struct view *view, struct line *line);
/* Select line */
void (*select)(struct view *view, struct line *line);
LINE_GRAPHIC_VLINE
};
-static int line_graphics[] = {
+static chtype line_graphics[] = {
/* LINE_GRAPHIC_VLINE: */ '|'
};
int len = 0;
int col = 0;
int trimmed = FALSE;
+ size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
if (max_len <= 0)
return 0;
if (opt_utf8) {
- len = utf8_length(string, &col, max_len, &trimmed, use_tilde);
+ len = utf8_length(&string, skip, &col, max_len, &trimmed, use_tilde);
} else {
col = len = strlen(string);
if (len > max_len) {
}
set_view_attr(view, type);
- waddnstr(view->win, string, len);
+ if (len > 0)
+ waddnstr(view->win, string, len);
if (trimmed && use_tilde) {
set_view_attr(view, LINE_DELIMITER);
waddch(view->win, '~');
while (spaces > 0) {
int len = MIN(spaces, sizeof(space) - 1);
- col += draw_chars(view, type, space, spaces, FALSE);
+ col += draw_chars(view, type, space, len, FALSE);
spaces -= len;
}
return col;
}
-static bool
-draw_lineno(struct view *view, unsigned int lineno)
-{
- char number[10];
- int digits3 = view->digits < 3 ? 3 : view->digits;
- int max_number = MIN(digits3, STRING_SIZE(number));
- int max = view->width - view->col;
- int col;
-
- if (max < max_number)
- max_number = max;
-
- lineno += view->offset + 1;
- if (lineno == 1 || (lineno % opt_num_interval) == 0) {
- static char fmt[] = "%1ld";
-
- if (view->digits <= 9)
- fmt[1] = '0' + digits3;
-
- if (!string_format(number, fmt, lineno))
- number[0] = 0;
- col = draw_chars(view, LINE_LINE_NUMBER, number, max_number, TRUE);
- } else {
- col = draw_space(view, LINE_LINE_NUMBER, max_number, max_number);
- }
-
- if (col < max) {
- set_view_attr(view, LINE_DEFAULT);
- waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
- col++;
- }
-
- if (col < max)
- col += draw_space(view, LINE_DEFAULT, max - col, 1);
- view->col += col;
-
- return view->width - view->col <= 0;
-}
-
static bool
draw_text(struct view *view, enum line_type type, const char *string, bool trim)
{
- view->col += draw_chars(view, type, string, view->width - view->col, trim);
- return view->width - view->col <= 0;
+ view->col += draw_chars(view, type, string, view->width + view->yoffset - view->col, trim);
+ return view->width + view->yoffset <= view->col;
}
static bool
draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
{
- int max = view->width - view->col;
+ size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
+ int max = view->width + view->yoffset - view->col;
int i;
if (max < size)
@@ -1966,22 +1932,21 @@ draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t si
set_view_attr(view, type);
/* Using waddch() instead of waddnstr() ensures that
* they'll be rendered correctly for the cursor line. */
- for (i = 0; i < size; i++)
+ for (i = skip; i < size; i++)
waddch(view->win, graphic[i]);
view->col += size;
- if (size < max) {
+ if (size < max && skip <= size)
waddch(view->win, ' ');
- view->col++;
- }
+ view->col++;
- return view->width - view->col <= 0;
+ return view->width + view->yoffset <= view->col;
}
static bool
draw_field(struct view *view, enum line_type type, const char *text, int len, bool trim)
{
- int max = MIN(view->width - view->col, len);
+ int max = MIN(view->width + view->yoffset - view->col, len);
int col;
if (text)
@@ -1989,8 +1954,9 @@ draw_field(struct view *view, enum line_type type, const char *text, int len, bo
else
col = draw_space(view, type, max - 1, max - 1);
- view->col += col + draw_space(view, LINE_DEFAULT, max - col, max - col);
- return view->width - view->col <= 0;
+ view->col += col;
+ view->col += draw_space(view, LINE_DEFAULT, max - col, max - col);
+ return view->width + view->yoffset <= view->col;
}
static bool
return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
}
+static bool
+draw_author(struct view *view, const char *author)
+{
+ bool trim = opt_author_cols == 0 || opt_author_cols > 5 || !author;
+
+ if (!trim) {
+ static char initials[10];
+ size_t pos;
+
+#define is_initial_sep(c) (isspace(c) || ispunct(c) || (c) == '@')
+
+ memset(initials, 0, sizeof(initials));
+ for (pos = 0; *author && pos < opt_author_cols - 1; author++, pos++) {
+ while (is_initial_sep(*author))
+ author++;
+ strncpy(&initials[pos], author, sizeof(initials) - 1 - pos);
+ while (*author && !is_initial_sep(author[1]))
+ author++;
+ }
+
+ author = initials;
+ }
+
+ return draw_field(view, LINE_AUTHOR, author, opt_author_cols, trim);
+}
+
+static bool
+draw_mode(struct view *view, mode_t mode)
+{
+ const char *str;
+
+ if (S_ISDIR(mode))
+ str = "drwxr-xr-x";
+ else if (S_ISLNK(mode))
+ str = "lrwxrwxrwx";
+ else if (S_ISGITLINK(mode))
+ str = "m---------";
+ else if (S_ISREG(mode) && mode & S_IXUSR)
+ str = "-rwxr-xr-x";
+ else if (S_ISREG(mode))
+ str = "-rw-r--r--";
+ else
+ str = "----------";
+
+ return draw_field(view, LINE_MODE, str, STRING_SIZE("-rw-r--r-- "), FALSE);
+}
+
+static bool
+draw_lineno(struct view *view, unsigned int lineno)
+{
+ char number[10];
+ int digits3 = view->digits < 3 ? 3 : view->digits;
+ int max = MIN(view->width + view->yoffset - view->col, digits3);
+ char *text = NULL;
+
+ lineno += view->offset + 1;
+ if (lineno == 1 || (lineno % opt_num_interval) == 0) {
+ static char fmt[] = "%1ld";
+
+ fmt[1] = '0' + (view->digits <= 9 ? digits3 : 1);
+ if (string_format(number, fmt, lineno))
+ text = number;
+ }
+ if (text)
+ view->col += 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, &line_graphics[LINE_GRAPHIC_VLINE], 1);
+}
+
static bool
draw_view_line(struct view *view, unsigned int lineno)
{
struct line *line;
bool selected = (view->offset + lineno == view->lineno);
- bool draw_ok;
assert(view_is_displayed(view));
view->ops->select(view, line);
}
- scrollok(view->win, FALSE);
- draw_ok = view->ops->draw(view, line, lineno);
- scrollok(view->win, TRUE);
-
- return draw_ok;
+ return view->ops->draw(view, line, lineno);
}
static void
if (!dirty)
return;
- redrawwin(view->win);
- if (input_mode)
- wnoutrefresh(view->win);
- else
- wrefresh(view->win);
+ wnoutrefresh(view->win);
}
static void
break;
}
- redrawwin(view->win);
- if (input_mode)
- wnoutrefresh(view->win);
- else
- wrefresh(view->win);
+ wnoutrefresh(view->win);
}
static void
mvwaddnstr(view->title, 0, 0, buf, bufpos);
wclrtoeol(view->title);
- wmove(view->title, 0, view->width - 1);
-
- if (input_mode)
- wnoutrefresh(view->title);
- else
- wrefresh(view->title);
+ wnoutrefresh(view->title);
}
static void
if (!view->win)
die("Failed to create %s view", view->name);
- scrollok(view->win, TRUE);
+ scrollok(view->win, FALSE);
view->title = newwin(1, 0, offset + view->height, 0);
if (!view->title)
}
static void
-update_display_cursor(struct view *view)
+toggle_view_option(bool *option, const char *help)
{
- /* Move the cursor to the right-most column of the cursor line.
- *
- * XXX: This could turn out to be a bit expensive, but it ensures that
- * the cursor does not jump around. */
- if (view->lines) {
- wmove(view->win, view->lineno - view->offset, view->width - 1);
- wrefresh(view->win);
- }
+ *option = !*option;
+ redraw_display(FALSE);
+ report("%sabling %s", *option ? "En" : "Dis", help);
}
static void
-toggle_view_option(bool *option, const char *help)
+maximize_view(struct view *view)
{
- *option = !*option;
+ memset(display, 0, sizeof(display));
+ current_view = 0;
+ display[current_view] = view;
+ resize_display();
redraw_display(FALSE);
- report("%sabling %s", *option ? "En" : "Dis", help);
+ report("");
}
+
/*
* Navigation
*/
+static bool
+goto_view_line(struct view *view, unsigned long offset, unsigned long lineno)
+{
+ if (lineno >= view->lines)
+ lineno = view->lines > 0 ? view->lines - 1 : 0;
+
+ if (offset > lineno || offset + view->height <= lineno) {
+ unsigned long half = view->height / 2;
+
+ if (lineno > half)
+ offset = lineno - half;
+ else
+ offset = 0;
+ }
+
+ if (offset != view->offset || lineno != view->lineno) {
+ view->offset = offset;
+ view->lineno = lineno;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int
+apply_step(double step, int value)
+{
+ if (step >= 1)
+ return (int) step;
+ value *= step + 0.01;
+ return value ? value : 1;
+}
+
/* Scrolling backend */
static void
do_scroll_view(struct view *view, int lines)
int line = lines > 0 ? view->height - lines : 0;
int end = line + ABS(lines);
+ scrollok(view->win, TRUE);
wscrl(view->win, lines);
+ scrollok(view->win, FALSE);
- for (; line < end; line++) {
- if (!draw_view_line(view, line))
- break;
- }
+ while (line < end && draw_view_line(view, line))
+ line++;
if (redraw_current_line)
draw_view_line(view, view->lineno - view->offset);
+ wnoutrefresh(view->win);
}
- redrawwin(view->win);
- wrefresh(view->win);
+ view->has_scrolled = TRUE;
report("");
}
assert(view_is_displayed(view));
switch (request) {
+ case REQ_SCROLL_LEFT:
+ if (view->yoffset == 0) {
+ report("Cannot scroll beyond the first column");
+ return;
+ }
+ if (view->yoffset <= apply_step(opt_hscroll, view->width))
+ view->yoffset = 0;
+ else
+ view->yoffset -= apply_step(opt_hscroll, view->width);
+ redraw_view_from(view, 0);
+ report("");
+ return;
+ case REQ_SCROLL_RIGHT:
+ view->yoffset += apply_step(opt_hscroll, view->width);
+ redraw_view(view);
+ report("");
+ return;
case REQ_SCROLL_PAGE_DOWN:
lines = view->height;
case REQ_SCROLL_LINE_DOWN:
/* Draw the current line */
draw_view_line(view, view->lineno - view->offset);
- redrawwin(view->win);
- wrefresh(view->win);
+ wnoutrefresh(view->win);
report("");
}
static void
select_view_line(struct view *view, unsigned long lineno)
{
- if (lineno - view->offset >= view->height) {
- view->offset = lineno;
- view->lineno = lineno;
- if (view_is_displayed(view))
- redraw_view(view);
-
- } else {
- unsigned long old_lineno = view->lineno - view->offset;
+ unsigned long old_lineno = view->lineno;
+ unsigned long old_offset = view->offset;
- view->lineno = lineno;
+ if (goto_view_line(view, view->offset, lineno)) {
if (view_is_displayed(view)) {
- draw_view_line(view, old_lineno);
- draw_view_line(view, view->lineno - view->offset);
- redrawwin(view->win);
- wrefresh(view->win);
+ if (old_offset != view->offset) {
+ redraw_view(view);
+ } else {
+ draw_view_line(view, old_lineno - view->offset);
+ draw_view_line(view, view->lineno - view->offset);
+ wnoutrefresh(view->win);
+ }
} else {
view->ops->select(view, &view->line[view->lineno]);
}
free(view->line);
view->p_offset = view->offset;
+ view->p_yoffset = view->yoffset;
view->p_lineno = view->lineno;
view->line = NULL;
view->offset = 0;
+ view->yoffset = 0;
view->lines = 0;
view->lineno = 0;
view->line_alloc = 0;
return FALSE;
}
- if (view->p_lineno >= view->lines) {
- view->p_lineno = view->lines > 0 ? view->lines - 1 : 0;
- if (view->p_offset >= view->p_lineno) {
- unsigned long half = view->height / 2;
-
- if (view->p_lineno > half)
- view->p_offset = view->p_lineno - half;
- else
- view->p_offset = 0;
- }
- }
-
- if (view_is_displayed(view) &&
- view->offset != view->p_offset &&
- view->lineno != view->p_lineno)
+ if (goto_view_line(view, view->p_offset, view->p_lineno) &&
+ view_is_displayed(view))
werase(view->win);
- view->offset = view->p_offset;
- view->lineno = view->p_lineno;
+ view->yoffset = view->p_yoffset;
view->p_restore = FALSE;
return TRUE;
return TRUE;
if (!io_can_read(view->pipe)) {
- if (view->lines == 0) {
+ if (view->lines == 0 && view_is_displayed(view)) {
time_t secs = time(NULL) - view->start_time;
- if (secs > view->update_secs) {
+ if (secs > 1 && secs > view->update_secs) {
if (view->update_secs == 0)
redraw_view(view);
update_view_title(view);
enum open_flags {
OPEN_DEFAULT = 0, /* Use default view switching. */
OPEN_SPLIT = 1, /* Split current view. */
- OPEN_BACKGROUNDED = 2, /* Backgrounded. */
OPEN_RELOAD = 4, /* Reload view even if it is the current. */
- OPEN_NOMAXIMIZE = 8, /* Do not maximize the current view. */
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)
{
- bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
bool split = !!(flags & OPEN_SPLIT);
bool reload = !!(flags & (OPEN_RELOAD | OPEN_REFRESH | OPEN_PREPARED));
- bool nomaximize = !!(flags & (OPEN_NOMAXIMIZE | OPEN_REFRESH));
+ bool nomaximize = !!(flags & OPEN_REFRESH);
struct view *view = VIEW(request);
int nviews = displayed_views();
struct view *base_view = display[0];
if (split) {
display[1] = view;
- if (!backgrounded)
- current_view = 1;
+ current_view = 1;
} else if (!nomaximize) {
/* Maximize the current view. */
memset(display, 0, sizeof(display));
}
if (prev && view != prev) {
- if (split && !backgrounded) {
+ if (split) {
/* "Blur" the previous view. */
update_view_title(prev);
}
redraw_view(view);
report("");
}
-
- /* If the view is backgrounded the above calls to report()
- * won't redraw the view title. */
- if (backgrounded)
- update_view_title(view);
}
static void
{
int i;
- if (request == REQ_NONE) {
- doupdate();
+ if (request == REQ_NONE)
return TRUE;
- }
if (request > REQ_NONE) {
open_run_request(request);
move_view(view, request);
break;
+ case REQ_SCROLL_LEFT:
+ case REQ_SCROLL_RIGHT:
case REQ_SCROLL_LINE_DOWN:
case REQ_SCROLL_LINE_UP:
case REQ_SCROLL_PAGE_DOWN:
case REQ_MAXIMIZE:
if (displayed_views() == 2)
- open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
+ maximize_view(view);
break;
case REQ_TOGGLE_LINENO:
* followed. */
if (view->parent &&
view->parent->parent != view->parent) {
- memset(display, 0, sizeof(display));
- current_view = 0;
- display[current_view] = view->parent;
+ maximize_view(view->parent);
view->parent = view;
- resize_display();
- redraw_display(FALSE);
- report("");
break;
}
/* Fall-through */
* View backend utilities
*/
+static void
+parse_timezone(time_t *time, const char *zone)
+{
+ long tz;
+
+ tz = ('0' - zone[1]) * 60 * 60 * 10;
+ tz += ('0' - zone[2]) * 60 * 60;
+ tz += ('0' - zone[3]) * 60;
+ tz += ('0' - zone[4]);
+
+ if (zone[0] == '-')
+ tz = -tz;
+
+ *time -= tz;
+}
+
/* Parse author lines where the name may be empty:
* author <email@address.tld> 1138474660 +0100
*/
@@ -3318,20 +3371,8 @@ parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
char *zone = strchr(secs, ' ');
time_t time = (time_t) atol(secs);
- if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
- long tz;
-
- zone++;
- tz = ('0' - zone[1]) * 60 * 60 * 10;
- tz += ('0' - zone[2]) * 60 * 60;
- tz += ('0' - zone[3]) * 60;
- tz += ('0' - zone[4]) * 60;
-
- if (zone[0] == '-')
- tz = -tz;
-
- time -= tz;
- }
+ if (zone && strlen(zone) == STRING_SIZE(" +0700"))
+ parse_timezone(&time, zone + 1);
gmtime_r(&time, tm);
}
}
static bool
-select_commit_parent(const char *id, char rev[SIZEOF_REV])
+select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
{
char buf[SIZEOF_STR * 4];
const char *revlist_argv[] = {
- "git", "rev-list", "-1", "--parents", id, NULL
+ "git", "rev-list", "-1", "--parents", id, "--", path, NULL
};
int parents;
return FALSE;
} else if (parents == 0) {
- report("The selected commit has no parents");
+ if (path)
+ report("Path '%s' does not exist in the parent", path);
+ else
+ report("The selected commit has no parents");
return FALSE;
}
static bool
pager_draw(struct view *view, struct line *line, unsigned int lineno)
{
- char *text = line->data;
+ char text[SIZEOF_STR];
if (opt_line_number && draw_lineno(view, lineno))
return TRUE;
+ string_expand(text, sizeof(text), line->data, opt_tab_size);
draw_text(view, line->type, text, TRUE);
return TRUE;
}
static bool
help_open(struct view *view)
{
- int lines = ARRAY_SIZE(req_info) + 2;
+ char buf[SIZEOF_STR];
+ size_t bufpos;
int i;
if (view->lines > 0)
return TRUE;
- for (i = 0; i < ARRAY_SIZE(req_info); i++)
- if (!req_info[i].request)
- lines++;
-
- lines += run_requests + 1;
-
- view->line = calloc(lines, sizeof(*view->line));
- if (!view->line)
- return FALSE;
-
add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT);
for (i = 0; i < ARRAY_SIZE(req_info); i++) {
if (!*key)
key = "(no key defined)";
- add_line_format(view, LINE_DEFAULT, " %-25s %s",
- key, req_info[i].help);
+ for (bufpos = 0; bufpos <= req_info[i].namelen; bufpos++) {
+ buf[bufpos] = tolower(req_info[i].name[bufpos]);
+ if (buf[bufpos] == '_')
+ buf[bufpos] = '-';
+ }
+
+ add_line_format(view, LINE_DEFAULT, " %-25s %-20s %s",
+ key, buf, req_info[i].help);
}
if (run_requests) {
for (i = 0; i < run_requests; i++) {
struct run_request *req = get_run_request(REQ_NONE + i + 1);
const char *key;
- char cmd[SIZEOF_STR];
- size_t bufpos;
int argc;
if (!req)
key = "(no key defined)";
for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
- if (!string_format_from(cmd, &bufpos, "%s%s",
+ if (!string_format_from(buf, &bufpos, "%s%s",
argc ? " " : "", req->argv[argc]))
return REQ_NONE;
add_line_format(view, LINE_DEFAULT, " %-10s %-14s `%s`",
- keymap_table[req->keymap].name, key, cmd);
+ keymap_table[req->keymap].name, key, buf);
}
return TRUE;
/* Parse output from git-ls-tree(1):
*
- * 100644 blob fb0e31ea6cc679b7379631188190e975f5789c26 Makefile
- * 100644 blob 5304ca4260aaddaee6498f9630e7d471b8591ea6 README
* 100644 blob f931e1d229c3e185caad4449bf5b66ed72462657 tig.c
- * 100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38 web.conf
*/
#define SIZEOF_TREE_ATTR \
- STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
+ STRING_SIZE("100644 blob f931e1d229c3e185caad4449bf5b66ed72462657\t")
#define SIZEOF_TREE_MODE \
STRING_SIZE("100644 ")
};
struct io io = {};
+ if (!view->lines) {
+ tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL);
+ report("Tree is empty");
+ return TRUE;
+ }
+
if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
report("Failed to load tree data");
return TRUE;
if (textlen <= SIZEOF_TREE_ATTR)
return FALSE;
if (view->lines == 0 &&
- !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
+ !tree_entry(view, LINE_TREE_HEAD, opt_path, NULL, NULL))
return FALSE;
/* Strip the path part ... */
{
struct tree_entry *entry = line->data;
- if (line->type == LINE_TREE_PARENT) {
+ if (line->type == LINE_TREE_HEAD) {
if (draw_text(view, line->type, "Directory path /", TRUE))
return TRUE;
} else {
- char mode[11] = "-r--r--r--";
-
- if (S_ISDIR(entry->mode)) {
- mode[3] = mode[6] = mode[9] = 'x';
- mode[0] = 'd';
- }
- if (S_ISLNK(entry->mode))
- mode[0] = 'l';
- if (entry->mode & S_IWUSR)
- mode[2] = 'w';
- if (entry->mode & S_IXUSR)
- mode[3] = 'x';
- if (entry->mode & S_IXGRP)
- mode[6] = 'x';
- if (entry->mode & S_IXOTH)
- mode[9] = 'x';
- if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
+ if (draw_mode(view, entry->mode))
return TRUE;
- if (opt_author &&
- draw_field(view, LINE_MAIN_AUTHOR, entry->author, opt_author_cols, TRUE))
+ if (opt_author && draw_author(view, entry->author))
return TRUE;
if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
switch (line->type) {
case LINE_TREE_DIR:
- /* Depending on whether it is a subdir or parent (updir?) link
+ /* Depending on whether it is a subdirectory or parent link
* mangle the path buffer. */
if (line == &view->line[1] && *opt_path) {
pop_tree_stack_entry();
struct blame {
struct blame_commit *commit;
+ unsigned long lineno;
char text[1];
};
{
struct blame_commit *commit;
struct blame *blame;
- const char *pos = text + SIZEOF_REV - 1;
+ const char *pos = text + SIZEOF_REV - 2;
+ size_t orig_lineno = 0;
size_t lineno;
size_t group;
- if (strlen(text) <= SIZEOF_REV || *pos != ' ')
+ if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
return NULL;
- if (!parse_number(&pos, &lineno, 1, view->lines) ||
+ if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
+ !parse_number(&pos, &lineno, 1, view->lines) ||
!parse_number(&pos, &group, 1, view->lines - lineno + 1))
return NULL;
blame = line->data;
blame->commit = commit;
+ blame->lineno = orig_lineno + group - 1;
line->dirty = 1;
}
size_t linelen = strlen(line);
struct blame *blame = malloc(sizeof(*blame) + linelen);
+ if (!blame)
+ return FALSE;
+
blame->commit = NULL;
strncpy(blame->text, line, linelen);
blame->text[linelen] = 0;
author_time = (time_t) atol(line);
} else if (match_blame_header("author-tz ", &line)) {
- long tz;
-
- tz = ('0' - line[1]) * 60 * 60 * 10;
- tz += ('0' - line[2]) * 60 * 60;
- tz += ('0' - line[3]) * 60;
- tz += ('0' - line[4]) * 60;
-
- if (line[0] == '-')
- tz = -tz;
-
- author_time -= tz;
+ parse_timezone(&author_time, line);
gmtime_r(&author_time, &commit->time);
} else if (match_blame_header("summary ", &line)) {
struct blame *blame = line->data;
struct tm *time = NULL;
const char *id = NULL, *author = NULL;
+ char text[SIZEOF_STR];
if (blame->commit && *blame->commit->filename) {
id = blame->commit->id;
if (opt_date && draw_date(view, time))
return TRUE;
- if (opt_author &&
- draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
+ if (opt_author && draw_author(view, author))
return TRUE;
if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
return TRUE;
- if (draw_lineno(view, lineno))
- return TRUE;
+ if (draw_lineno(view, lineno))
+ return TRUE;
+
+ string_expand(text, sizeof(text), blame->text, opt_tab_size);
+ draw_text(view, LINE_DEFAULT, text, TRUE);
+ return TRUE;
+}
+
+static bool
+check_blame_commit(struct blame *blame, bool check_null_id)
+{
+ if (!blame->commit)
+ report("Commit data not loaded yet");
+ else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
+ report("No commit exist for the selected line");
+ else
+ return TRUE;
+ return FALSE;
+}
+
+static void
+setup_blame_parent_line(struct view *view, struct blame *blame)
+{
+ const char *diff_tree_argv[] = {
+ "git", "diff-tree", "-U0", blame->commit->id,
+ "--", blame->commit->filename, NULL
+ };
+ struct io io = {};
+ int parent_lineno = -1;
+ int blamed_lineno = -1;
+ char *line;
+
+ if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
+ return;
+
+ while ((line = io_get(&io, '\n', TRUE))) {
+ if (*line == '@') {
+ char *pos = strchr(line, '+');
+
+ parent_lineno = atoi(line + 4);
+ if (pos)
+ blamed_lineno = atoi(pos + 1);
- draw_text(view, LINE_DEFAULT, blame->text, TRUE);
- return TRUE;
-}
+ } else if (*line == '+' && parent_lineno != -1) {
+ if (blame->lineno == blamed_lineno - 1 &&
+ !strcmp(blame->text, line + 1)) {
+ view->lineno = parent_lineno ? parent_lineno - 1 : 0;
+ break;
+ }
+ blamed_lineno++;
+ }
+ }
-static bool
-check_blame_commit(struct blame *blame)
-{
- if (!blame->commit)
- report("Commit data not loaded yet");
- else if (!strcmp(blame->commit->id, NULL_ID))
- report("No commit exist for the selected line");
- else
- return TRUE;
- return FALSE;
+ done_io(&io);
}
static enum request
switch (request) {
case REQ_VIEW_BLAME:
- if (check_blame_commit(blame)) {
+ if (check_blame_commit(blame, TRUE)) {
string_copy(opt_ref, blame->commit->id);
+ string_copy(opt_file, blame->commit->filename);
+ if (blame->lineno)
+ view->lineno = blame->lineno;
open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
}
break;
case REQ_PARENT:
- if (check_blame_commit(blame) &&
- select_commit_parent(blame->commit->id, opt_ref))
+ if (check_blame_commit(blame, TRUE) &&
+ select_commit_parent(blame->commit->id, opt_ref,
+ blame->commit->filename)) {
+ string_copy(opt_file, blame->commit->filename);
+ setup_blame_parent_line(view, blame);
open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
+ }
break;
case REQ_ENTER:
- if (!blame->commit) {
- report("No commit loaded yet");
+ if (!check_blame_commit(blame, FALSE))
break;
- }
if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
!strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
static bool
status_run(struct view *view, const char *argv[], char status, enum line_type type)
{
- struct status *file = NULL;
struct status *unmerged = NULL;
char *buf;
struct io io = {};
@@ -4600,6 +4664,8 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
add_line_data(view, NULL, type);
while ((buf = io_get(&io, 0, TRUE))) {
+ struct status *file = unmerged;
+
if (!file) {
file = calloc(1, sizeof(*file));
if (!file || !add_line_data(view, file, type))
@@ -4612,7 +4678,7 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
if (status == 'A')
string_copy(file->old.rev, NULL_ID);
- } else if (!file->status) {
+ } else if (!file->status || file == unmerged) {
if (!status_get_diff(file, buf, strlen(buf)))
goto error_out;
@@ -4620,21 +4686,13 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
if (!buf)
break;
- /* Collapse all 'M'odified entries that follow a
- * associated 'U'nmerged entry. */
- if (file->status == 'U') {
- unmerged = file;
-
- } else if (unmerged) {
- int collapse = !strcmp(buf, unmerged->new.name);
-
+ /* Collapse all modified entries that follow an
+ * associated unmerged entry. */
+ if (unmerged == file) {
+ unmerged->status = 'U';
unmerged = NULL;
- if (collapse) {
- free(file);
- file = NULL;
- view->lines--;
- continue;
- }
+ } else if (file->status == 'U') {
+ unmerged = file;
}
}
view->p_restore = FALSE;
}
+static void
+status_update_onbranch(void)
+{
+ static const char *paths[][2] = {
+ { "rebase-apply/rebasing", "Rebasing" },
+ { "rebase-apply/applying", "Applying mailbox" },
+ { "rebase-apply/", "Rebasing mailbox" },
+ { "rebase-merge/interactive", "Interactive rebase" },
+ { "rebase-merge/", "Rebase merge" },
+ { "MERGE_HEAD", "Merging" },
+ { "BISECT_LOG", "Bisecting" },
+ { "HEAD", "On branch" },
+ };
+ char buf[SIZEOF_STR];
+ struct stat stat;
+ int i;
+
+ if (is_initial_commit()) {
+ string_copy(status_onbranch, "Initial commit");
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(paths); i++) {
+ char *head = opt_head;
+
+ if (!string_format(buf, "%s/%s", opt_git_dir, paths[i][0]) ||
+ lstat(buf, &stat) < 0)
+ continue;
+
+ if (!*opt_head) {
+ struct io io = {};
+
+ if (string_format(buf, "%s/rebase-merge/head-name", opt_git_dir) &&
+ io_open(&io, buf) &&
+ io_read_buf(&io, buf, sizeof(buf))) {
+ head = chomp_string(buf);
+ if (!prefixcmp(head, "refs/heads/"))
+ head += STRING_SIZE("refs/heads/");
+ }
+ }
+
+ if (!string_format(status_onbranch, "%s %s", paths[i][1], head))
+ string_copy(status_onbranch, opt_head);
+ return;
+ }
+
+ string_copy(status_onbranch, "Not currently on any branch");
+}
+
/* First parse staged info using git-diff-index(1), then parse unstaged
* info using git-diff-files(1), and finally untracked files using
* git-ls-files(1). */
reset_view(view);
add_line_data(view, NULL, LINE_STAT_HEAD);
- if (is_initial_commit())
- string_copy(status_onbranch, "Initial commit");
- else if (!*opt_head)
- string_copy(status_onbranch, "Not currently on any branch");
- else if (!string_format(status_onbranch, "On branch %s", opt_head))
- return FALSE;
+ status_update_onbranch();
run_io_bg(update_index_argv);
case LINE_STAT_NONE:
type = LINE_DEFAULT;
- text = " (no files)";
+ text = " (no files)";
break;
case LINE_STAT_HEAD:
return TRUE;
}
+static enum request
+status_load_error(struct view *view, struct view *stage, const char *path)
+{
+ if (displayed_views() == 2 || display[current_view] != view)
+ maximize_view(view);
+ report("Failed to load '%s': %s", path, io_strerror(&stage->io));
+ return REQ_NONE;
+}
+
static enum request
status_enter(struct view *view, struct line *line)
{
};
if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
- return REQ_QUIT;
+ return status_load_error(view, stage, newpath);
} else {
const char *index_show_argv[] = {
"git", "diff-index", "--root", "--patch-with-stat",
};
if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
- return REQ_QUIT;
+ return status_load_error(view, stage, newpath);
}
if (status)
};
if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
- return REQ_QUIT;
+ return status_load_error(view, stage, newpath);
if (status)
info = "Unstaged changes to %s";
else
}
if (!prepare_update_file(stage, newpath))
- return REQ_QUIT;
+ return status_load_error(view, stage, newpath);
info = "Untracked file %s";
break;
return FALSE;
result = status_update_write(&io, status, type);
- done_io(&io);
- return result;
+ return done_io(&io) && result;
}
static bool
status_update_files(struct view *view, struct line *line)
{
+ char buf[sizeof(view->ref)];
struct io io = {};
bool result = TRUE;
struct line *pos = view->line + view->lines;
int files = 0;
int file, done;
+ int cursor_y, cursor_x;
if (!status_update_prepare(&io, line->type))
return FALSE;
for (pos = line; pos < view->line + view->lines && pos->data; pos++)
files++;
- for (file = 0, done = 0; result && file < files; line++, file++) {
+ string_copy(buf, view->ref);
+ getsyx(cursor_y, cursor_x);
+ for (file = 0, done = 5; result && file < files; line++, file++) {
int almost_done = file * 100 / files;
if (almost_done > done) {
string_format(view->ref, "updating file %u of %u (%d%% done)",
file, files, done);
update_view_title(view);
+ setsyx(cursor_y, cursor_x);
+ doupdate();
}
result = status_update_write(&io, line->data, line->type);
}
+ string_copy(view->ref, buf);
- done_io(&io);
- return result;
+ return done_io(&io) && result;
}
static bool
return FALSE;
} else {
+ char mode[10] = "100644";
+ const char *reset_argv[] = {
+ "git", "update-index", "--cacheinfo", mode,
+ status->old.rev, status->old.name, NULL
+ };
const char *checkout_argv[] = {
"git", "checkout", "--", status->old.name, NULL
};
if (!prompt_yesno("Are you sure you want to overwrite any changes?"))
return FALSE;
- return run_io_fg(checkout_argv, opt_cdup);
+ string_format(mode, "%o", status->old.mode);
+ return (status->status != 'U' || run_io_fg(reset_argv, opt_cdup)) &&
+ run_io_fg(checkout_argv, opt_cdup);
}
}
/* After returning the status view has been split to
* show the stage view. No further reloading is
* necessary. */
- status_enter(view, line);
- return REQ_NONE;
+ return status_enter(view, line);
case REQ_REFRESH:
/* Simply reload the view. */
}
VIEW(REQ_VIEW_STATUS)->p_restore = TRUE;
- open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
+ open_view(view, REQ_VIEW_STATUS, OPEN_REFRESH);
/* Check whether the staged entry still exists, and close the
* stage view if it doesn't. */
if (opt_date && draw_date(view, &commit->time))
return TRUE;
- if (opt_author &&
- draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
+ if (opt_author && draw_author(view, commit->author))
return TRUE;
if (opt_rev_graph && commit->graph_size &&
/* FIXME: More graceful handling of titles; append "..." to
* shortened titles, etc. */
- string_ncopy(commit->title, line, strlen(line));
+ string_expand(commit->title, sizeof(commit->title), line, 1);
view->line[view->lines - 1].dirty = 1;
}
/*
* Unicode / UTF-8 handling
*
- * NOTE: Much of the following code for dealing with unicode is derived from
+ * NOTE: Much of the following code for dealing with Unicode is derived from
* ELinks' UTF-8 code developed by Scrool <scroolik@gmail.com>. Origin file is
- * src/intl/charset.c from the utf8 branch commit elinks-0.11.0-g31f2c28.
+ * src/intl/charset.c from the UTF-8 branch commit elinks-0.11.0-g31f2c28.
*/
-/* I've (over)annotated a lot of code snippets because I am not entirely
- * confident that the approach taken by this small UTF-8 interface is correct.
- * --jonas */
-
static inline int
unicode_width(unsigned long c)
{
3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 5,5,5,5,6,6,1,1,
};
-/* Decode UTF-8 multi-byte representation into a unicode character. */
+/* Decode UTF-8 multi-byte representation into a Unicode character. */
static inline unsigned long
utf8_to_unicode(const char *string, size_t length)
{
unicode += (string[5] & 0x3f);
break;
default:
- die("Invalid unicode length");
+ die("Invalid Unicode length");
}
/* Invalid characters could return the special 0xfffd value but NUL
*
* Returns the number of bytes to output from string to satisfy max_width. */
static size_t
-utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve)
+utf8_length(const char **start, size_t skip, int *width, size_t max_width, int *trimmed, bool reserve)
{
- const char *start = string;
+ const char *string = *start;
const char *end = strchr(string, '\0');
unsigned char last_bytes = 0;
size_t last_ucwidth = 0;
@@ -6087,11 +6204,15 @@ utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool
* it is a single- or double-width character. */
unicode = utf8_to_unicode(string, bytes);
- /* FIXME: Graceful handling of invalid unicode character. */
+ /* FIXME: Graceful handling of invalid Unicode character. */
if (!unicode)
break;
ucwidth = unicode_width(unicode);
+ if (skip > 0) {
+ skip -= ucwidth <= skip ? ucwidth : skip;
+ *start += bytes;
+ }
*width += ucwidth;
if (*width > max_width) {
*trimmed = 1;
@@ -6104,11 +6225,11 @@ utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool
}
string += bytes;
- last_bytes = bytes;
+ last_bytes = ucwidth ? bytes : 0;
last_ucwidth = ucwidth;
}
- return string - start;
+ return string - *start;
}
@@ -6119,10 +6240,17 @@ utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool
/* Whether or not the curses interface has been initialized. */
static bool cursed = FALSE;
+/* Terminal hacks and workarounds. */
+static bool use_scroll_redrawwin;
+static bool use_scroll_status_wclear;
+
/* The status window is used for polling keystrokes. */
static WINDOW *status_win;
-static bool status_empty = TRUE;
+/* Reading from the prompt? */
+static bool input_mode = FALSE;
+
+static bool status_empty = FALSE;
/* Update status and title window. */
static void
va_start(args, msg);
wmove(status_win, 0, 0);
+ if (view->has_scrolled && use_scroll_status_wclear)
+ wclear(status_win);
if (*msg) {
vwprintw(status_win, msg, args);
status_empty = FALSE;
status_empty = TRUE;
}
wclrtoeol(status_win);
- wrefresh(status_win);
+ wnoutrefresh(status_win);
va_end(args);
}
update_view_title(view);
- update_display_cursor(view);
}
/* Controls when nodelay should be in effect when polling user input. */
static void
init_display(void)
{
+ const char *term;
int x, y;
/* Initialize the curses library */
if (!cursed)
die("Failed to initialize curses");
- nonl(); /* Tell curses not to do NL->CR/NL on output */
+ nonl(); /* Disable conversion and detect newlines from input. */
cbreak(); /* Take input chars one at a time, no wait for \n */
noecho(); /* Don't echo input */
- leaveok(stdscr, TRUE);
+ leaveok(stdscr, FALSE);
if (has_colors())
init_colors();
if (opt_line_graphics) {
line_graphics[LINE_GRAPHIC_VLINE] = ACS_VLINE;
}
+
+ term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
+ if (term && !strcmp(term, "gnome-terminal")) {
+ /* In the gnome-terminal-emulator, the message from
+ * scrolling up one line when impossible followed by
+ * scrolling down one line causes corruption of the
+ * status line. This is fixed by calling wclear. */
+ use_scroll_status_wclear = TRUE;
+ use_scroll_redrawwin = FALSE;
+
+ } else if (term && !strcmp(term, "xrvt-xpm")) {
+ /* No problems with full optimizations in xrvt-(unicode)
+ * and aterm. */
+ use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
+
+ } else {
+ /* When scrolling in (u)xterm the last line in the
+ * scrolling direction will update slowly. */
+ use_scroll_redrawwin = TRUE;
+ use_scroll_status_wclear = FALSE;
+ }
}
static int
-get_input(bool prompting)
+get_input(int prompt_position)
{
struct view *view;
- int i, key;
+ int i, key, cursor_y, cursor_x;
- if (prompting)
+ if (prompt_position)
input_mode = TRUE;
- while (true) {
- foreach_view (view, i)
+ while (TRUE) {
+ foreach_view (view, i) {
update_view(view);
+ if (view_is_displayed(view) && view->has_scrolled &&
+ use_scroll_redrawwin)
+ redrawwin(view->win);
+ view->has_scrolled = FALSE;
+ }
+
+ /* Update the cursor position. */
+ if (prompt_position) {
+ getbegyx(status_win, cursor_y, cursor_x);
+ cursor_x = prompt_position;
+ } else {
+ view = display[current_view];
+ getbegyx(view->win, cursor_y, cursor_x);
+ cursor_x = view->width - 1;
+ cursor_y += view->lineno - view->offset;
+ }
+ setsyx(cursor_y, cursor_x);
/* Refresh, accept single keystroke of input */
+ doupdate();
key = wgetch(status_win);
/* wgetch() with nodelay() enabled returns ERR when
* there's no input. */
if (key == ERR) {
- doupdate();
} else if (key == KEY_RESIZE) {
int height, width;
getmaxyx(stdscr, height, width);
- /* Resize the status view and let the view driver take
- * care of resizing the displayed views. */
- resize_display();
- redraw_display(TRUE);
wresize(status_win, 1, width);
mvwin(status_win, height - 1, 0);
- wrefresh(status_win);
+ wnoutrefresh(status_win);
+ resize_display();
+ redraw_display(TRUE);
} else {
input_mode = FALSE;
mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
wclrtoeol(status_win);
- key = get_input(TRUE);
+ key = get_input(pos + 1);
switch (key) {
case KEY_RETURN:
case KEY_ENTER:
* Repository properties
*/
-static int
-git_properties(const char **argv, const char *separators,
- int (*read_property)(char *, size_t, char *, size_t))
-{
- struct io io = {};
-
- if (init_io_rd(&io, argv, NULL, FORMAT_NONE))
- return read_properties(&io, separators, read_property);
- return ERR;
-}
-
static struct ref *refs = NULL;
static size_t refs_alloc = 0;
static size_t refs_size = 0;
}
if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
- /* it's an annotated tag, replace the previous sha1 with the
+ /* it's an annotated tag, replace the previous SHA1 with the
* resolved commit id; relies on the fact git-ls-remote lists
* the commit id of an annotated tag right before the commit id
* it points to. */
load_refs(void)
{
static const char *ls_remote_argv[SIZEOF_ARG] = {
- "git", "ls-remote", ".", NULL
+ "git", "ls-remote", opt_git_dir, NULL
};
static bool init = FALSE;
while (id_refs_size > 0)
free(id_refs[--id_refs_size]);
- return git_properties(ls_remote_argv, "\t", read_ref);
+ return run_io_load(ls_remote_argv, "\t", read_ref);
+}
+
+static void
+set_remote_branch(const char *name, const char *value, size_t valuelen)
+{
+ if (!strcmp(name, ".remote")) {
+ string_ncopy(opt_remote, value, valuelen);
+
+ } else if (*opt_remote && !strcmp(name, ".merge")) {
+ size_t from = strlen(opt_remote);
+
+ if (!prefixcmp(value, "refs/heads/"))
+ value += STRING_SIZE("refs/heads/");
+
+ if (!string_format_from(opt_remote, &from, "/%s", value))
+ opt_remote[0] = 0;
+ }
+}
+
+static void
+set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
+{
+ const char *argv[SIZEOF_ARG] = { name, "=" };
+ int argc = 1 + (cmd == option_set_command);
+ int error = ERR;
+
+ if (!argv_from_string(argv, &argc, value))
+ config_msg = "Too many option arguments";
+ else
+ error = cmd(argc, argv);
+
+ if (error == ERR)
+ warn("Option 'tig.%s': %s", name, config_msg);
+}
+
+static bool
+set_environment_variable(const char *name, const char *value)
+{
+ size_t len = strlen(name) + 1 + strlen(value) + 1;
+ char *env = malloc(len);
+
+ if (env &&
+ string_nformat(env, len, NULL, "%s=%s", name, value) &&
+ putenv(env) == 0)
+ return TRUE;
+ free(env);
+ return FALSE;
+}
+
+static void
+set_work_tree(const char *value)
+{
+ char cwd[SIZEOF_STR];
+
+ if (!getcwd(cwd, sizeof(cwd)))
+ die("Failed to get cwd path: %s", strerror(errno));
+ if (chdir(opt_git_dir) < 0)
+ die("Failed to chdir(%s): %s", strerror(errno));
+ if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
+ die("Failed to get git path: %s", strerror(errno));
+ if (chdir(cwd) < 0)
+ die("Failed to chdir(%s): %s", cwd, strerror(errno));
+ if (chdir(value) < 0)
+ die("Failed to chdir(%s): %s", value, strerror(errno));
+ if (!getcwd(cwd, sizeof(cwd)))
+ die("Failed to get cwd path: %s", strerror(errno));
+ if (!set_environment_variable("GIT_WORK_TREE", cwd))
+ die("Failed to set GIT_WORK_TREE to '%s'", cwd);
+ if (!set_environment_variable("GIT_DIR", opt_git_dir))
+ die("Failed to set GIT_DIR to '%s'", opt_git_dir);
+ opt_is_inside_work_tree = TRUE;
}
static int
@@ -6554,30 +6780,24 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen
if (!strcmp(name, "i18n.commitencoding"))
string_ncopy(opt_encoding, value, valuelen);
- if (!strcmp(name, "core.editor"))
+ else if (!strcmp(name, "core.editor"))
string_ncopy(opt_editor, value, valuelen);
- /* branch.<head>.remote */
- if (*opt_head &&
- !strncmp(name, "branch.", 7) &&
- !strncmp(name + 7, opt_head, strlen(opt_head)) &&
- !strcmp(name + 7 + strlen(opt_head), ".remote"))
- string_ncopy(opt_remote, value, valuelen);
+ else if (!strcmp(name, "core.worktree"))
+ set_work_tree(value);
- if (*opt_head && *opt_remote &&
- !strncmp(name, "branch.", 7) &&
- !strncmp(name + 7, opt_head, strlen(opt_head)) &&
- !strcmp(name + 7 + strlen(opt_head), ".merge")) {
- size_t from = strlen(opt_remote);
+ else if (!prefixcmp(name, "tig.color."))
+ set_repo_config_option(name + 10, value, option_color_command);
- if (!prefixcmp(value, "refs/heads/")) {
- value += STRING_SIZE("refs/heads/");
- valuelen -= STRING_SIZE("refs/heads/");
- }
+ else if (!prefixcmp(name, "tig.bind."))
+ set_repo_config_option(name + 9, value, option_bind_command);
- if (!string_format_from(opt_remote, &from, "/%s", value))
- opt_remote[0] = 0;
- }
+ else if (!prefixcmp(name, "tig."))
+ set_repo_config_option(name + 4, value, option_set_command);
+
+ else if (*opt_head && !prefixcmp(name, "branch.") &&
+ !strncmp(name + 7, opt_head, strlen(opt_head)))
+ set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
return OK;
}
{
const char *config_list_argv[] = { "git", GIT_CONFIG, "--list", NULL };
- return git_properties(config_list_argv, "=", read_repo_config_option);
+ return run_io_load(config_list_argv, "=", read_repo_config_option);
}
static int
}
}
- return git_properties(rev_parse_argv, "=", read_repo_info);
-}
-
-static int
-read_properties(struct io *io, const char *separators,
- int (*read_property)(char *, size_t, char *, size_t))
-{
- char *name;
- int state = OK;
-
- if (!start_io(io))
- return ERR;
-
- while (state == OK && (name = io_get(io, '\n', TRUE))) {
- char *value;
- size_t namelen;
- size_t valuelen;
-
- name = chomp_string(name);
- namelen = strcspn(name, separators);
-
- if (name[namelen]) {
- name[namelen] = 0;
- value = chomp_string(name + namelen + 1);
- valuelen = strlen(value);
-
- } else {
- value = "";
- valuelen = 0;
- }
-
- state = read_property(name, namelen, value, valuelen);
- }
-
- if (state != ERR && io_error(io))
- state = ERR;
- done_io(io);
-
- return state;
+ return run_io_load(rev_parse_argv, "=", read_repo_info);
}
* Main
*/
+static const char usage[] =
+"tig " TIG_VERSION " (" __DATE__ ")\n"
+"\n"
+"Usage: tig [options] [revs] [--] [paths]\n"
+" or: tig show [options] [revs] [--] [paths]\n"
+" or: tig blame [rev] path\n"
+" or: tig status\n"
+" or: tig < [git command output]\n"
+"\n"
+"Options:\n"
+" -v, --version Show version and exit\n"
+" -h, --help Show help message and exit";
+
static void __NORETURN
quit(int sig)
{
va_end(args);
}
+static enum request
+parse_options(int argc, const char *argv[])
+{
+ enum request request = REQ_VIEW_MAIN;
+ const char *subcommand;
+ bool seen_dashdash = FALSE;
+ /* XXX: This is vulnerable to the user overriding options
+ * required for the main view parser. */
+ const char *custom_argv[SIZEOF_ARG] = {
+ "git", "log", "--no-color", "--pretty=raw", "--parents",
+ "--topo-order", NULL
+ };
+ int i, j = 6;
+
+ if (!isatty(STDIN_FILENO)) {
+ io_open(&VIEW(REQ_VIEW_PAGER)->io, "");
+ return REQ_VIEW_PAGER;
+ }
+
+ if (argc <= 1)
+ return REQ_NONE;
+
+ subcommand = argv[1];
+ if (!strcmp(subcommand, "status")) {
+ if (argc > 2)
+ warn("ignoring arguments after `%s'", subcommand);
+ return REQ_VIEW_STATUS;
+
+ } else if (!strcmp(subcommand, "blame")) {
+ if (argc <= 2 || argc > 4)
+ 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++;
+ }
+
+ string_ncopy(opt_file, argv[i], strlen(argv[i]));
+ return REQ_VIEW_BLAME;
+
+ } else if (!strcmp(subcommand, "show")) {
+ request = REQ_VIEW_DIFF;
+
+ } else {
+ subcommand = NULL;
+ }
+
+ if (subcommand) {
+ custom_argv[1] = subcommand;
+ j = 2;
+ }
+
+ for (i = 1 + !!subcommand; i < argc; i++) {
+ const char *opt = argv[i];
+
+ if (seen_dashdash || !strcmp(opt, "--")) {
+ seen_dashdash = TRUE;
+
+ } else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
+ printf("tig version %s\n", TIG_VERSION);
+ quit(0);
+
+ } else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
+ printf("%s\n", usage);
+ quit(0);
+ }
+
+ custom_argv[j++] = opt;
+ if (j >= ARRAY_SIZE(custom_argv))
+ die("command too long");
+ }
+
+ if (!prepare_update(VIEW(request), custom_argv, NULL, FORMAT_NONE))
+ die("Failed to format arguments");
+
+ return request;
+}
+
int
main(int argc, const char *argv[])
{
- const char **run_argv = NULL;
+ enum request request = parse_options(argc, argv);
struct view *view;
- enum request request;
size_t i;
signal(SIGINT, quit);
+ signal(SIGPIPE, SIG_IGN);
if (setlocale(LC_ALL, "")) {
char *codeset = nl_langinfo(CODESET);
if (load_git_config() == ERR)
die("Failed to load repo config.");
- request = parse_options(argc, argv, &run_argv);
- if (request == REQ_NONE)
- return 0;
-
/* Require a git repository unless when running in pager mode. */
if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
die("Not a git repository");
init_display();
- if (request == REQ_VIEW_PAGER || run_argv) {
- if (request == REQ_VIEW_PAGER)
- io_open(&VIEW(request)->io, "");
- else if (!prepare_update(VIEW(request), run_argv, NULL, FORMAT_NONE))
- die("Failed to format arguments");
+ if (request != REQ_NONE)
open_view(NULL, request, OPEN_PREPARED);
- request = REQ_NONE;
- }
+ request = request == REQ_NONE ? REQ_VIEW_MAIN : REQ_NONE;
while (view_driver(display[current_view], request)) {
- int key = get_input(FALSE);
+ int key = get_input(0);
view = display[current_view];
request = get_keybinding(view->keymap, key);
{
char *cmd = read_prompt(":");
- if (cmd) {
+ if (cmd && isdigit(*cmd)) {
+ int lineno = view->lineno + 1;
+
+ if (parse_int(&lineno, cmd, 1, view->lines + 1) == OK) {
+ select_view_line(view, lineno - 1);
+ report("");
+ } else {
+ report("Unable to parse '%s' as a line number", cmd);
+ }
+
+ } else if (cmd) {
struct view *next = VIEW(REQ_VIEW_PAGER);
const char *argv[SIZEOF_ARG] = { "git" };
int argc = 1;
if (search)
string_ncopy(opt_search, search, strlen(search));
+ else if (*opt_search)
+ request = request == REQ_SEARCH ?
+ REQ_FIND_NEXT :
+ REQ_FIND_PREV;
else
request = REQ_NONE;
break;