Code

Add missing NULL in blame_grep
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index e25ba686ea8b26ebc8f21b2e95cdfa2bc44c97ad..2a3ab3afc1f1f3b292cec1a8f5c2b7f76cdfae2b 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -109,7 +109,6 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma
 
 /* The default interval between line numbers. */
 #define NUMBER_INTERVAL        5
-#define SCROLL_INTERVAL        1
 
 #define TAB_SIZE       8
 
@@ -117,6 +116,8 @@ static size_t utf8_length(const char **string, size_t col, int *width, size_t ma
 
 #define NULL_ID                "0000000000000000000000000000000000000000"
 
+#define S_ISGITLINK(mode) (((mode) & S_IFMT) == 0160000)
+
 #ifndef GIT_CONFIG
 #define GIT_CONFIG "config"
 #endif
@@ -160,6 +161,27 @@ typedef enum input_status (*input_handler)(void *data, char *buf, int c);
 static char *prompt_input(const char *prompt, input_handler handler, void *data);
 static bool prompt_yesno(const char *prompt);
 
+/*
+ * Allocation helpers ... Entering macro hell to never be seen again.
+ */
+
+#define DEFINE_ALLOCATOR(name, type, chunk_size)                               \
+static type *                                                                  \
+name(type **mem, size_t size, size_t increase)                                 \
+{                                                                              \
+       size_t num_chunks = (size + chunk_size - 1) / chunk_size;               \
+       size_t num_chunks_new = (size + increase + chunk_size - 1) / chunk_size;\
+       type *tmp = *mem;                                                       \
+                                                                               \
+       if (mem == NULL || num_chunks != num_chunks_new) {                      \
+               tmp = realloc(tmp, num_chunks_new * chunk_size * sizeof(type)); \
+               if (tmp)                                                        \
+                       *mem = tmp;                                             \
+       }                                                                       \
+                                                                               \
+       return tmp;                                                             \
+}
+
 /*
  * String helpers
  */
@@ -188,20 +210,6 @@ string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen)
 #define string_add(dst, from, src) \
        string_ncopy_do(dst + (from), sizeof(dst) - (from), src, sizeof(src))
 
-static size_t
-string_expand_length(const char *line, int tabsize)
-{
-       size_t size, pos;
-
-       for (pos = 0; line[pos]; pos++) {
-               if (line[pos] == '\t' && tabsize > 0)
-                       size += tabsize - (size % tabsize);
-               else
-                       size++;
-       }
-       return size;
-}
-
 static void
 string_expand(char *dst, size_t dstlen, const char *src, int tabsize)
 {
@@ -291,7 +299,7 @@ struct enum_map {
 #define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
 
 static bool
-map_enum_do(struct enum_map *map, size_t map_size, int *value, const char *name)
+map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
 {
        size_t namelen = strlen(name);
        int i;
@@ -322,6 +330,17 @@ suffixcmp(const char *str, int slen, const char *suffix)
 }
 
 
+static const char *
+mkdate(const time_t *time)
+{
+       static char buf[DATE_COLS + 1];
+       struct tm tm;
+
+       gmtime_r(time, &tm);
+       return strftime(buf, sizeof(buf), DATE_FORMAT, &tm) ? buf : NULL;
+}
+
+
 static bool
 argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
 {
@@ -412,6 +431,8 @@ io_open(struct io *io, const char *name)
 {
        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;
 }
 
@@ -571,7 +592,7 @@ io_error(struct io *io)
        return io->error;
 }
 
-static bool
+static char *
 io_strerror(struct io *io)
 {
        return strerror(io->error);
@@ -605,20 +626,14 @@ io_read(struct io *io, void *buf, size_t bufsize)
        } while (1);
 }
 
+DEFINE_ALLOCATOR(realloc_io_buf, char, BUFSIZ)
+
 static char *
 io_get(struct io *io, int c, bool can_read)
 {
        char *eol;
        ssize_t readsize;
 
-       if (!io->buf) {
-               io->buf = io->bufpos = malloc(BUFSIZ);
-               if (!io->buf)
-                       return NULL;
-               io->bufalloc = BUFSIZ;
-               io->bufsize = 0;
-       }
-
        while (TRUE) {
                if (io->bufsize > 0) {
                        eol = memchr(io->bufpos, c, io->bufsize);
@@ -647,6 +662,12 @@ io_get(struct io *io, int c, bool can_read)
                if (io->bufsize > 0 && io->bufpos > io->buf)
                        memmove(io->buf, io->bufpos, io->bufsize);
 
+               if (io->bufalloc == io->bufsize) {
+                       if (!realloc_io_buf(&io->buf, io->bufalloc, BUFSIZ))
+                               return NULL;
+                       io->bufalloc += BUFSIZ;
+               }
+
                io->bufpos = io->buf;
                readsize = io_read(io, io->buf + io->bufsize, io->bufalloc - io->bufsize);
                if (io_error(io))
@@ -678,14 +699,14 @@ io_write(struct io *io, const void *buf, size_t bufsize)
 static bool
 io_read_buf(struct io *io, char buf[], size_t bufsize)
 {
-       bool error;
+       char *result = io_get(io, '\n', TRUE);
 
-       io->buf = io->bufpos = buf;
-       io->bufalloc = bufsize;
-       error = !io_get(io, '\n', TRUE) && io_error(io);
-       io->buf = NULL;
+       if (result) {
+               result = chomp_string(result);
+               string_ncopy_do(buf, bufsize, result, strlen(result));
+       }
 
-       return done_io(io) || error;
+       return done_io(io) && result;
 }
 
 static bool
@@ -838,7 +859,7 @@ struct request_info {
        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
@@ -873,6 +894,7 @@ static bool opt_line_graphics               = 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]       = "";
@@ -1061,7 +1083,7 @@ struct keybinding {
        enum request request;
 };
 
-static struct keybinding default_keybindings[] = {
+static const struct keybinding default_keybindings[] = {
        /* View switching */
        { 'm',          REQ_VIEW_MAIN },
        { 'd',          REQ_VIEW_DIFF },
@@ -1147,7 +1169,7 @@ enum keymap {
 #undef KEYMAP_
 };
 
-static struct enum_map keymap_table[] = {
+static const struct enum_map keymap_table[] = {
 #define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
        KEYMAP_INFO
 #undef KEYMAP_
@@ -1202,7 +1224,7 @@ struct key {
        int value;
 };
 
-static struct key key_table[] = {
+static const struct key key_table[] = {
        { "Enter",      KEY_RETURN },
        { "Space",      ' ' },
        { "Backspace",  KEY_BACKSPACE },
@@ -1280,7 +1302,7 @@ get_key(enum request request)
        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;
@@ -1303,6 +1325,8 @@ struct run_request {
 static struct run_request *run_request;
 static size_t run_requests;
 
+DEFINE_ALLOCATOR(realloc_run_requests, struct run_request, 8)
+
 static enum request
 add_run_request(enum keymap keymap, int key, int argc, const char **argv)
 {
@@ -1311,11 +1335,9 @@ add_run_request(enum keymap keymap, int key, int argc, const char **argv)
        if (argc >= ARRAY_SIZE(req->argv) - 1)
                return REQ_NONE;
 
-       req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
-       if (!req)
+       if (!realloc_run_requests(&run_request, run_requests, 1))
                return REQ_NONE;
 
-       run_request = req;
        req = &run_request[run_requests];
        req->keymap = keymap;
        req->key = key;
@@ -1364,7 +1386,11 @@ add_builtin_run_requests(void)
  * User config file handling.
  */
 
-static struct enum_map color_map[] = {
+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),
@@ -1377,7 +1403,7 @@ static struct enum_map color_map[] = {
        COLOR_MAP(YELLOW),
 };
 
-static struct enum_map attr_map[] = {
+static const struct enum_map attr_map[] = {
 #define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
        ATTR_MAP(NORMAL),
        ATTR_MAP(BLINK),
@@ -1388,12 +1414,52 @@ static struct enum_map attr_map[] = {
        ATTR_MAP(UNDERLINE),
 };
 
-#define set_color(color, name)         map_enum(color, color_map, 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;
+}
+
+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
@@ -1408,7 +1474,7 @@ option_color_command(int argc, const char *argv[])
 
        info = get_line_info(argv[0]);
        if (!info) {
-               static struct enum_map obsolete[] = {
+               static const struct enum_map obsolete[] = {
                        ENUM_MAP("main-delim",  LINE_DELIMITER),
                        ENUM_MAP("main-date",   LINE_DATE),
                        ENUM_MAP("main-author", LINE_AUTHOR),
@@ -1443,16 +1509,6 @@ static int parse_bool(bool *opt, const char *arg)
        return OK;
 }
 
-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;
-}
-
 static int
 parse_string(char *opt, const char *arg, size_t optsize)
 {
@@ -1467,7 +1523,7 @@ parse_string(char *opt, const char *arg, size_t optsize)
                }
                arg += 1; arglen -= 2;
        default:
-               string_ncopy_do(opt, optsize, arg, strlen(arg));
+               string_ncopy_do(opt, optsize, arg, arglen);
                return OK;
        }
 }
@@ -1510,6 +1566,9 @@ option_set_command(int argc, const char *argv[])
        if (!strcmp(argv[0], "author-width"))
                return parse_int(&opt_author_cols, argv[2], 0, 1024);
 
+       if (!strcmp(argv[0], "horizontal-scroll"))
+               return parse_step(&opt_hscroll, argv[2]);
+
        if (!strcmp(argv[0], "tab-size"))
                return parse_int(&opt_tab_size, argv[2], 1, 1024);
 
@@ -1546,7 +1605,7 @@ option_bind_command(int argc, const char *argv[])
 
        request = get_request(argv[2]);
        if (request == REQ_NONE) {
-               static struct enum_map obsolete[] = {
+               static const struct enum_map obsolete[] = {
                        ENUM_MAP("cherry-pick",         REQ_NONE),
                        ENUM_MAP("screen-resize",       REQ_NONE),
                        ENUM_MAP("tree-parent",         REQ_PARENT),
@@ -1736,7 +1795,6 @@ struct view {
        /* Buffering */
        size_t lines;           /* Total number of lines */
        struct line *line;      /* Line index */
-       size_t line_alloc;      /* Total number of allocated lines */
        unsigned int digits;    /* Number of digits in the lines member. */
 
        /* Drawing */
@@ -1744,7 +1802,6 @@ struct view {
        enum line_type curtype; /* Attribute currently used for drawing. */
        unsigned long col;      /* Column when drawing. */
        bool has_scrolled;      /* View was scrolled. */
-       bool can_hscroll;       /* View can be scrolled horizontally. */
 
        /* Loading */
        struct io io;
@@ -1817,7 +1874,7 @@ enum line_graphic {
        LINE_GRAPHIC_VLINE
 };
 
-static int line_graphics[] = {
+static chtype line_graphics[] = {
        /* LINE_GRAPHIC_VLINE: */ '|'
 };
 
@@ -1865,9 +1922,6 @@ draw_chars(struct view *view, enum line_type type, const char *string,
                col++;
        }
 
-       if (view->col + col >= view->width + view->yoffset)
-               view->can_hscroll = TRUE;
-
        return col;
 }
 
@@ -1882,66 +1936,25 @@ draw_space(struct view *view, enum line_type type, int max, int spaces)
        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)
-{
-       size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
-       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 && skip <= col) {
-               set_view_attr(view, LINE_DEFAULT);
-               waddch(view->win, line_graphics[LINE_GRAPHIC_VLINE]);
-       }
-       col++;
-
-       view->col += col;
-       if (col < max && skip <= col)
-               col = draw_space(view, LINE_DEFAULT, max - col, 1);
-       view->col++;
-
-       return view->width + view->yoffset <= view->col;
-}
-
 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->yoffset - view->col, trim);
-       return view->width - view->col <= 0;
+       return view->width + view->yoffset <= view->col;
 }
 
 static bool
 draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t size)
 {
        size_t skip = view->yoffset > view->col ? view->yoffset - view->col : 0;
-       int max = view->width - view->col;
+       int max = view->width + view->yoffset - view->col;
        int i;
 
        if (max < size)
@@ -1958,13 +1971,13 @@ draw_graphic(struct view *view, enum line_type type, chtype graphic[], size_t si
                waddch(view->win, ' ');
        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)
@@ -1978,15 +1991,9 @@ draw_field(struct view *view, enum line_type type, const char *text, int len, bo
 }
 
 static bool
-draw_date(struct view *view, struct tm *time)
+draw_date(struct view *view, time_t *time)
 {
-       char buf[DATE_COLS];
-       char *date;
-       int timelen = 0;
-
-       if (time)
-               timelen = strftime(buf, sizeof(buf), DATE_FORMAT, time);
-       date = timelen ? buf : NULL;
+       const char *date = mkdate(time);
 
        return draw_field(view, LINE_DATE, date, DATE_COLS, FALSE);
 }
@@ -2020,22 +2027,45 @@ draw_author(struct view *view, const char *author)
 static bool
 draw_mode(struct view *view, mode_t mode)
 {
-       static const char dir_mode[]    = "drwxr-xr-x";
-       static const char link_mode[]   = "lrwxrwxrwx";
-       static const char exe_mode[]    = "-rwxr-xr-x";
-       static const char file_mode[]   = "-rw-r--r--";
        const char *str;
 
        if (S_ISDIR(mode))
-               str = dir_mode;
+               str = "drwxr-xr-x";
        else if (S_ISLNK(mode))
-               str = link_mode;
-       else if (mode & S_IXUSR)
-               str = exe_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 = file_mode;
+               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";
 
-       return draw_field(view, LINE_MODE, str, sizeof(file_mode), FALSE);
+               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
@@ -2095,9 +2125,6 @@ redraw_view_from(struct view *view, int lineno)
 {
        assert(0 <= lineno && lineno < view->height);
 
-       if (lineno == 0)
-               view->can_hscroll = FALSE;
-
        for (; lineno < view->height; lineno++) {
                if (!draw_view_line(view, lineno))
                        break;
@@ -2242,10 +2269,55 @@ toggle_view_option(bool *option, const char *help)
        report("%sabling %s", *option ? "En" : "Dis", help);
 }
 
+static void
+maximize_view(struct view *view)
+{
+       memset(display, 0, sizeof(display));
+       current_view = 0;
+       display[current_view] = view;
+       resize_display();
+       redraw_display(FALSE);
+       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)
@@ -2307,19 +2379,15 @@ scroll_view(struct view *view, enum request request)
                        report("Cannot scroll beyond the first column");
                        return;
                }
-               if (view->yoffset <= SCROLL_INTERVAL)
+               if (view->yoffset <= apply_step(opt_hscroll, view->width))
                        view->yoffset = 0;
                else
-                       view->yoffset -= SCROLL_INTERVAL;
+                       view->yoffset -= apply_step(opt_hscroll, view->width);
                redraw_view_from(view, 0);
                report("");
                return;
        case REQ_SCROLL_RIGHT:
-               if (!view->can_hscroll) {
-                       report("Cannot scroll beyond the last column");
-                       return;
-               }
-               view->yoffset += SCROLL_INTERVAL;
+               view->yoffset += apply_step(opt_hscroll, view->width);
                redraw_view(view);
                report("");
                return;
@@ -2454,23 +2522,34 @@ move_view(struct view *view, enum request request)
 
 static void search_view(struct view *view, enum request request);
 
+static bool
+grep_text(struct view *view, const char *text[])
+{
+       regmatch_t pmatch;
+       size_t i;
+
+       for (i = 0; text[i]; i++)
+               if (*text[i] &&
+                   regexec(view->regex, text[i], 1, &pmatch, 0) != REG_NOMATCH)
+                       return TRUE;
+       return FALSE;
+}
+
 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);
+       unsigned long old_lineno = view->lineno;
+       unsigned long old_offset = view->offset;
 
-       } else {
-               unsigned long old_lineno = view->lineno - 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);
-                       wnoutrefresh(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]);
                }
@@ -2572,7 +2651,6 @@ reset_view(struct view *view)
        view->yoffset = 0;
        view->lines  = 0;
        view->lineno = 0;
-       view->line_alloc = 0;
        view->vid[0] = 0;
        view->update_secs = 0;
 }
@@ -2662,26 +2740,11 @@ restore_view_position(struct view *view)
                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->yoffset = view->p_yoffset;
-       view->lineno = view->p_lineno;
        view->p_restore = FALSE;
 
        return TRUE;
@@ -2758,36 +2821,6 @@ begin_update(struct view *view, bool refresh)
        return TRUE;
 }
 
-#define ITEM_CHUNK_SIZE 256
-static void *
-realloc_items(void *mem, size_t *size, size_t new_size, size_t item_size)
-{
-       size_t num_chunks = *size / ITEM_CHUNK_SIZE;
-       size_t num_chunks_new = (new_size + ITEM_CHUNK_SIZE - 1) / ITEM_CHUNK_SIZE;
-
-       if (mem == NULL || num_chunks != num_chunks_new) {
-               *size = num_chunks_new * ITEM_CHUNK_SIZE;
-               mem = realloc(mem, *size * item_size);
-       }
-
-       return mem;
-}
-
-static struct line *
-realloc_lines(struct view *view, size_t line_size)
-{
-       size_t alloc = view->line_alloc;
-       struct line *tmp = realloc_items(view->line, &alloc, line_size,
-                                        sizeof(*view->line));
-
-       if (!tmp)
-               return NULL;
-
-       view->line = tmp;
-       view->line_alloc = alloc;
-       return view->line;
-}
-
 static bool
 update_view(struct view *view)
 {
@@ -2802,7 +2835,7 @@ update_view(struct view *view)
                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 > 1 && secs > view->update_secs) {
@@ -2878,12 +2911,14 @@ update_view(struct view *view)
        return TRUE;
 }
 
+DEFINE_ALLOCATOR(realloc_lines, struct line, 256)
+
 static struct line *
 add_line_data(struct view *view, void *data, enum line_type type)
 {
        struct line *line;
 
-       if (!realloc_lines(view, view->lines + 1))
+       if (!realloc_lines(&view->line, view->lines, 1))
                return NULL;
 
        line = &view->line[view->lines++];
@@ -3075,10 +3110,8 @@ view_driver(struct view *view, enum request request)
 {
        int i;
 
-       if (request == REQ_NONE) {
-               doupdate();
+       if (request == REQ_NONE)
                return TRUE;
-       }
 
        if (request > REQ_NONE) {
                open_run_request(request);
@@ -3219,7 +3252,7 @@ view_driver(struct view *view, enum request request)
 
        case REQ_MAXIMIZE:
                if (displayed_views() == 2)
-                       open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
+                       maximize_view(view);
                break;
 
        case REQ_TOGGLE_LINENO:
@@ -3283,13 +3316,8 @@ view_driver(struct view *view, enum request request)
                 * 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 */
@@ -3309,6 +3337,44 @@ view_driver(struct view *view, enum request request)
  * View backend utilities
  */
 
+DEFINE_ALLOCATOR(realloc_authors, const char *, 256)
+
+/* Small author cache to reduce memory consumption. It uses binary
+ * search to lookup or find place to position new entries. No entries
+ * are ever freed. */
+static const char *
+get_author(const char *name)
+{
+       static const char **authors;
+       static size_t authors_size;
+       int from = 0, to = authors_size - 1;
+
+       while (from <= to) {
+               size_t pos = (to + from) / 2;
+               int cmp = strcmp(name, authors[pos]);
+
+               if (!cmp)
+                       return authors[pos];
+
+               if (cmp < 0)
+                       to = pos - 1;
+               else
+                       from = pos + 1;
+       }
+
+       if (!realloc_authors(&authors, authors_size, 1))
+               return NULL;
+       name = strdup(name);
+       if (!name)
+               return NULL;
+
+       memmove(authors + from + 1, authors + from, (authors_size - from) * sizeof(*authors));
+       authors[from] = name;
+       authors_size++;
+
+       return name;
+}
+
 static void
 parse_timezone(time_t *time, const char *zone)
 {
@@ -3329,7 +3395,7 @@ parse_timezone(time_t *time, const char *zone)
  *     author  <email@address.tld> 1138474660 +0100
  */
 static void
-parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
+parse_author_line(char *ident, const char **author, time_t *time)
 {
        char *nameend = strchr(ident, '<');
        char *emailend = strchr(ident, '>');
@@ -3344,18 +3410,17 @@ parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
                        ident = "Unknown";
        }
 
-       string_ncopy_do(author, authorsize, ident, strlen(ident));
+       *author = get_author(ident);
 
        /* Parse epoch and timezone */
        if (emailend && emailend[1] == ' ') {
                char *secs = emailend + 2;
                char *zone = strchr(secs, ' ');
-               time_t time = (time_t) atol(secs);
 
-               if (zone && strlen(zone) == STRING_SIZE(" +0700"))
-                       parse_timezone(&time, zone + 1);
+               *time = (time_t) atol(secs);
 
-               gmtime_r(&time, tm);
+               if (zone && strlen(zone) == STRING_SIZE(" +0700"))
+                       parse_timezone(time, zone + 1);
        }
 }
 
@@ -3378,22 +3443,24 @@ select_commit_parent_handler(void *data, char *buf, int c)
 }
 
 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;
 
        if (!run_io_buf(revlist_argv, buf, sizeof(buf)) ||
-           !*chomp_string(buf) ||
            (parents = (strlen(buf) / 40) - 1) < 0) {
                report("Failed to get parent information");
                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;
        }
 
@@ -3434,13 +3501,9 @@ static bool
 add_describe_ref(char *buf, size_t *bufpos, const char *commit_id, const char *sep)
 {
        const char *describe_argv[] = { "git", "describe", commit_id, NULL };
-       char refbuf[SIZEOF_STR];
-       char *ref = NULL;
-
-       if (run_io_buf(describe_argv, refbuf, sizeof(refbuf)))
-               ref = chomp_string(refbuf);
+       char ref[SIZEOF_STR];
 
-       if (!ref || !*ref)
+       if (!run_io_buf(describe_argv, ref, sizeof(ref)) || !*ref)
                return TRUE;
 
        /* This is the only fatal call, since it can "corrupt" the buffer. */
@@ -3546,16 +3609,9 @@ pager_request(struct view *view, enum request request, struct line *line)
 static bool
 pager_grep(struct view *view, struct line *line)
 {
-       regmatch_t pmatch;
-       char *text = line->data;
-
-       if (!*text)
-               return FALSE;
+       const char *text[] = { line->data, NULL };
 
-       if (regexec(view->regex, text, 1, &pmatch, 0) == REG_NOMATCH)
-               return FALSE;
-
-       return TRUE;
+       return grep_text(view, text);
 }
 
 static void
@@ -3773,8 +3829,8 @@ push_tree_stack_entry(const char *name, unsigned long lineno)
 struct tree_entry {
        char id[SIZEOF_REV];
        mode_t mode;
-       struct tm time;                 /* Date from the author ident. */
-       char author[75];                /* Author of the commit. */
+       time_t time;                    /* Date from the author ident. */
+       const char *author;             /* Author of the commit. */
        char name[1];
 };
 
@@ -3817,8 +3873,8 @@ tree_entry(struct view *view, enum line_type type, const char *path,
 static bool
 tree_read_date(struct view *view, char *text, bool *read_date)
 {
-       static char author_name[SIZEOF_STR];
-       static struct tm author_time;
+       static const char *author_name;
+       static time_t author_time;
 
        if (!text && *read_date) {
                *read_date = FALSE;
@@ -3851,7 +3907,7 @@ tree_read_date(struct view *view, char *text, bool *read_date)
 
        } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
                parse_author_line(text + STRING_SIZE("author "),
-                                 author_name, sizeof(author_name), &author_time);
+                                 &author_name, &author_time);
 
        } else if (*text == ':') {
                char *pos;
@@ -3874,12 +3930,12 @@ tree_read_date(struct view *view, char *text, bool *read_date)
                        struct line *line = &view->line[i];
                        struct tree_entry *entry = line->data;
 
-                       annotated += !!*entry->author;
-                       if (*entry->author || strcmp(entry->name, text))
+                       annotated += !!entry->author;
+                       if (entry->author || strcmp(entry->name, text))
                                continue;
 
-                       string_copy(entry->author, author_name);
-                       memcpy(&entry->time, &author_time, sizeof(entry->time));
+                       entry->author = author_name;
+                       entry->time = author_time;
                        line->dirty = 1;
                        break;
                }
@@ -3967,7 +4023,7 @@ tree_draw(struct view *view, struct line *line, unsigned int lineno)
                if (opt_author && draw_author(view, entry->author))
                        return TRUE;
 
-               if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
+               if (opt_date && draw_date(view, entry->author ? &entry->time : NULL))
                        return TRUE;
        }
        if (draw_text(view, line->type, entry->name, TRUE))
@@ -4071,6 +4127,20 @@ tree_request(struct view *view, enum request request, struct line *line)
        return REQ_NONE;
 }
 
+static bool
+tree_grep(struct view *view, struct line *line)
+{
+       struct tree_entry *entry = line->data;
+       const char *text[] = {
+               entry->name,
+               opt_author ? entry->author : "",
+               opt_date ? mkdate(&entry->time) : "",
+               NULL
+       };
+
+       return grep_text(view, text);
+}
+
 static void
 tree_select(struct view *view, struct line *line)
 {
@@ -4098,7 +4168,7 @@ static struct view_ops tree_ops = {
        tree_read,
        tree_draw,
        tree_request,
-       pager_grep,
+       tree_grep,
        tree_select,
 };
 
@@ -4163,14 +4233,15 @@ static const char *blame_cat_file_argv[] = {
 struct blame_commit {
        char id[SIZEOF_REV];            /* SHA1 ID. */
        char title[128];                /* First line of the commit message. */
-       char author[75];                /* Author of the commit. */
-       struct tm time;                 /* Date from the author ident. */
+       const char *author;             /* Author of the commit. */
+       time_t time;                    /* Date from the author ident. */
        char filename[128];             /* Name of file. */
        bool has_previous;              /* Was a "previous" line detected. */
 };
 
 struct blame {
        struct blame_commit *commit;
+       unsigned long lineno;
        char text[1];
 };
 
@@ -4234,14 +4305,16 @@ parse_blame_commit(struct view *view, const char *text, int *blamed)
 {
        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;
 
@@ -4255,6 +4328,7 @@ parse_blame_commit(struct view *view, const char *text, int *blamed)
 
                blame = line->data;
                blame->commit = commit;
+               blame->lineno = orig_lineno + group - 1;
                line->dirty = 1;
        }
 
@@ -4282,14 +4356,15 @@ blame_read_file(struct view *view, const char *line, bool *read_file)
                return FALSE;
 
        } else {
-               size_t linelen = string_expand_length(line, opt_tab_size);
+               size_t linelen = strlen(line);
                struct blame *blame = malloc(sizeof(*blame) + linelen);
 
                if (!blame)
                        return FALSE;
 
                blame->commit = NULL;
-               string_expand(blame->text, linelen + 1, line, opt_tab_size);
+               strncpy(blame->text, line, linelen);
+               blame->text[linelen] = 0;
                return add_line_data(view, blame, LINE_BLAME_ID) != NULL;
        }
 }
@@ -4311,7 +4386,6 @@ blame_read(struct view *view, char *line)
 {
        static struct blame_commit *commit = NULL;
        static int blamed = 0;
-       static time_t author_time;
        static bool read_file = TRUE;
 
        if (read_file)
@@ -4336,14 +4410,13 @@ blame_read(struct view *view, char *line)
                              view->lines ? blamed * 100 / view->lines : 0);
 
        } else if (match_blame_header("author ", &line)) {
-               string_ncopy(commit->author, line, strlen(line));
+               commit->author = get_author(line);
 
        } else if (match_blame_header("author-time ", &line)) {
-               author_time = (time_t) atol(line);
+               commit->time = (time_t) atol(line);
 
        } else if (match_blame_header("author-tz ", &line)) {
-               parse_timezone(&author_time, line);
-               gmtime_r(&author_time, &commit->time);
+               parse_timezone(&commit->time, line);
 
        } else if (match_blame_header("summary ", &line)) {
                string_ncopy(commit->title, line, strlen(line));
@@ -4363,8 +4436,9 @@ static bool
 blame_draw(struct view *view, struct line *line, unsigned int lineno)
 {
        struct blame *blame = line->data;
-       struct tm *time = NULL;
+       time_t *time = NULL;
        const char *id = NULL, *author = NULL;
+       char text[SIZEOF_STR];
 
        if (blame->commit && *blame->commit->filename) {
                id = blame->commit->id;
@@ -4384,22 +4458,59 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno)
        if (draw_lineno(view, lineno))
                return TRUE;
 
-       draw_text(view, LINE_DEFAULT, blame->text, 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)
+check_blame_commit(struct blame *blame, bool check_null_id)
 {
        if (!blame->commit)
                report("Commit data not loaded yet");
-       else if (!strcmp(blame->commit->id, NULL_ID))
+       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);
+
+               } 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++;
+               }
+       }
+
+       done_io(&io);
+}
+
 static enum request
 blame_request(struct view *view, enum request request, struct line *line)
 {
@@ -4408,23 +4519,28 @@ blame_request(struct view *view, enum request request, struct line *line)
 
        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))
@@ -4468,27 +4584,16 @@ blame_grep(struct view *view, struct line *line)
 {
        struct blame *blame = line->data;
        struct blame_commit *commit = blame->commit;
-       regmatch_t pmatch;
-
-#define MATCH(text, on)                                                        \
-       (on && *text && regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
-
-       if (commit) {
-               char buf[DATE_COLS + 1];
-
-               if (MATCH(commit->title, 1) ||
-                   MATCH(commit->author, opt_author) ||
-                   MATCH(commit->id, opt_date))
-                       return TRUE;
-
-               if (strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time) &&
-                   MATCH(buf, 1))
-                       return TRUE;
-       }
-
-       return MATCH(blame->text, 1);
+       const char *text[] = {
+               blame->text,
+               commit ? commit->title : "",
+               commit ? commit->id : "",
+               commit && opt_author ? commit->author : "",
+               commit && opt_date ? mkdate(&commit->time) : "",
+               NULL
+       };
 
-#undef MATCH
+       return grep_text(view, text);
 }
 
 static void
@@ -4541,6 +4646,8 @@ static enum line_type stage_line_type;
 static size_t stage_chunks;
 static int *stage_chunk;
 
+DEFINE_ALLOCATOR(realloc_ints, int, 32)
+
 /* This should work even for the "On branch" line. */
 static inline bool
 status_has_none(struct view *view, struct line *line)
@@ -4741,7 +4848,7 @@ status_update_onbranch(void)
                        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);
+                               head = buf;
                                if (!prefixcmp(head, "refs/heads/"))
                                        head += STRING_SIZE("refs/heads/");
                        }
@@ -4837,6 +4944,15 @@ status_draw(struct view *view, struct line *line, unsigned int lineno)
        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)
 {
@@ -4864,7 +4980,7 @@ 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",
@@ -4873,7 +4989,7 @@ status_enter(struct view *view, struct line *line)
                        };
 
                        if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
-                               return REQ_QUIT;
+                               return status_load_error(view, stage, newpath);
                }
 
                if (status)
@@ -4890,7 +5006,7 @@ status_enter(struct view *view, struct line *line)
                };
 
                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
@@ -4909,7 +5025,7 @@ status_enter(struct view *view, struct line *line)
                }
 
                if (!prepare_update_file(stage, newpath))
-                       return REQ_QUIT;
+                       return status_load_error(view, stage, newpath);
                info = "Untracked file %s";
                break;
 
@@ -5027,18 +5143,19 @@ status_update_file(struct status *status, enum line_type type)
                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;
@@ -5046,7 +5163,9 @@ status_update_files(struct view *view, struct line *line)
        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) {
@@ -5054,12 +5173,14 @@ status_update_files(struct view *view, struct line *line)
                        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
@@ -5168,8 +5289,7 @@ status_request(struct view *view, enum request request, struct line *line)
                /* 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. */
@@ -5235,29 +5355,12 @@ static bool
 status_grep(struct view *view, struct line *line)
 {
        struct status *status = line->data;
-       enum { S_STATUS, S_NAME, S_END } state;
-       char buf[2] = "?";
-       regmatch_t pmatch;
 
-       if (!status)
-               return FALSE;
-
-       for (state = S_STATUS; state < S_END; state++) {
-               const char *text;
-
-               switch (state) {
-               case S_NAME:    text = status->new.name;        break;
-               case S_STATUS:
-                       buf[0] = status->status;
-                       text = buf;
-                       break;
+       if (status) {
+               const char buf[2] = { status->status, 0 };
+               const char *text[] = { status->new.name, buf, NULL };
 
-               default:
-                       return FALSE;
-               }
-
-               if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
-                       return TRUE;
+               return grep_text(view, text);
        }
 
        return FALSE;
@@ -5399,21 +5502,15 @@ stage_next(struct view *view, struct line *line)
        int i;
 
        if (!stage_chunks) {
-               static size_t alloc = 0;
-               int *tmp;
-
                for (line = view->line; line < view->line + view->lines; line++) {
                        if (line->type != LINE_DIFF_CHUNK)
                                continue;
 
-                       tmp = realloc_items(stage_chunk, &alloc,
-                                           stage_chunks, sizeof(*tmp));
-                       if (!tmp) {
+                       if (!realloc_ints(&stage_chunk, stage_chunks, 1)) {
                                report("Allocation failure");
                                return;
                        }
 
-                       stage_chunk = tmp;
                        stage_chunk[stage_chunks++] = line - view->line;
                }
        }
@@ -5526,8 +5623,8 @@ static struct view_ops stage_ops = {
 struct commit {
        char id[SIZEOF_REV];            /* SHA1 ID. */
        char title[128];                /* First line of the commit message. */
-       char author[75];                /* Author of the commit. */
-       struct tm time;                 /* Date from the author ident. */
+       const char *author;             /* Author of the commit. */
+       time_t time;                    /* Date from the author ident. */
        struct ref **refs;              /* Repository references. */
        chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
        size_t graph_size;              /* The width of the graph array. */
@@ -5753,7 +5850,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno)
 {
        struct commit *commit = line->data;
 
-       if (!*commit->author)
+       if (!commit->author)
                return FALSE;
 
        if (opt_date && draw_date(view, &commit->time))
@@ -5815,7 +5912,7 @@ main_read(struct view *view, char *line)
                if (view->lines > 0) {
                        commit = view->line[view->lines - 1].data;
                        view->line[view->lines - 1].dirty = 1;
-                       if (!*commit->author) {
+                       if (!commit->author) {
                                view->lines--;
                                free(commit);
                                graph->commit = NULL;
@@ -5866,8 +5963,7 @@ main_read(struct view *view, char *line)
 
        case LINE_AUTHOR:
                parse_author_line(line + STRING_SIZE("author "),
-                                 commit->author, sizeof(commit->author),
-                                 &commit->time);
+                                 &commit->author, &commit->time);
                update_rev_graph(view, graph);
                graph = graph->next;
                break;
@@ -5924,7 +6020,7 @@ grep_refs(struct ref **refs, regex_t *regex)
        regmatch_t pmatch;
        size_t i = 0;
 
-       if (!refs)
+       if (!opt_show_refs || !refs)
                return FALSE;
        do {
                if (regexec(regex, refs[i]->name, 1, &pmatch, 0) != REG_NOMATCH)
@@ -5938,42 +6034,14 @@ static bool
 main_grep(struct view *view, struct line *line)
 {
        struct commit *commit = line->data;
-       enum { S_TITLE, S_AUTHOR, S_DATE, S_REFS, S_END } state;
-       char buf[DATE_COLS + 1];
-       regmatch_t pmatch;
-
-       for (state = S_TITLE; state < S_END; state++) {
-               char *text;
-
-               switch (state) {
-               case S_TITLE:   text = commit->title;   break;
-               case S_AUTHOR:
-                       if (!opt_author)
-                               continue;
-                       text = commit->author;
-                       break;
-               case S_DATE:
-                       if (!opt_date)
-                               continue;
-                       if (!strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time))
-                               continue;
-                       text = buf;
-                       break;
-               case S_REFS:
-                       if (!opt_show_refs)
-                               continue;
-                       if (grep_refs(commit->refs, view->regex) == TRUE)
-                               return TRUE;
-                       continue;
-               default:
-                       return FALSE;
-               }
-
-               if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
-                       return TRUE;
-       }
+       const char *text[] = {
+               commit->title,
+               opt_author ? commit->author : "",
+               opt_date ? mkdate(&commit->time) : "",
+               NULL
+       };
 
-       return FALSE;
+       return grep_text(view, text) || grep_refs(commit->refs, view->regex);
 }
 
 static void
@@ -6444,14 +6512,16 @@ read_prompt(const char *prompt)
  */
 
 static struct ref *refs = NULL;
-static size_t refs_alloc = 0;
 static size_t refs_size = 0;
 
 /* Id <-> ref store */
 static struct ref ***id_refs = NULL;
-static size_t id_refs_alloc = 0;
 static size_t id_refs_size = 0;
 
+DEFINE_ALLOCATOR(realloc_refs, struct ref, 256)
+DEFINE_ALLOCATOR(realloc_refs_list, struct ref *, 8)
+DEFINE_ALLOCATOR(realloc_refs_lists, struct ref **, 8)
+
 static int
 compare_refs(const void *ref1_, const void *ref2_)
 {
@@ -6474,9 +6544,7 @@ compare_refs(const void *ref1_, const void *ref2_)
 static struct ref **
 get_refs(const char *id)
 {
-       struct ref ***tmp_id_refs;
        struct ref **ref_list = NULL;
-       size_t ref_list_alloc = 0;
        size_t ref_list_size = 0;
        size_t i;
 
@@ -6484,34 +6552,21 @@ get_refs(const char *id)
                if (!strcmp(id, id_refs[i][0]->id))
                        return id_refs[i];
 
-       tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
-                                   sizeof(*id_refs));
-       if (!tmp_id_refs)
+       if (!realloc_refs_lists(&id_refs, id_refs_size, 1))
                return NULL;
 
-       id_refs = tmp_id_refs;
-
        for (i = 0; i < refs_size; i++) {
-               struct ref **tmp;
-
                if (strcmp(id, refs[i].id))
                        continue;
 
-               tmp = realloc_items(ref_list, &ref_list_alloc,
-                                   ref_list_size + 1, sizeof(*ref_list));
-               if (!tmp) {
-                       if (ref_list)
-                               free(ref_list);
-                       return NULL;
-               }
+               if (!realloc_refs_list(&ref_list, ref_list_size, 1))
+                       return ref_list;
 
-               ref_list = tmp;
                ref_list[ref_list_size] = &refs[i];
                /* XXX: The properties of the commit chains ensures that we can
                 * safely modify the shared ref. The repo references will
                 * always be similar for the same id. */
                ref_list[ref_list_size]->next = 1;
-
                ref_list_size++;
        }
 
@@ -6575,8 +6630,8 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen)
 
                return OK;
        }
-       refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
-       if (!refs)
+
+       if (!realloc_refs(&refs, refs_size, 1))
                return ERR;
 
        ref = &refs[refs_size++];
@@ -6599,8 +6654,11 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen)
 static int
 load_refs(void)
 {
+       const char *head_argv[] = {
+               "git", "symbolic-ref", "HEAD", NULL
+       };
        static const char *ls_remote_argv[SIZEOF_ARG] = {
-               "git", "ls-remote", ".", NULL
+               "git", "ls-remote", opt_git_dir, NULL
        };
        static bool init = FALSE;
 
@@ -6612,6 +6670,13 @@ load_refs(void)
        if (!*opt_git_dir)
                return OK;
 
+       if (run_io_buf(head_argv, opt_head, sizeof(opt_head)) &&
+           !prefixcmp(opt_head, "refs/heads/")) {
+               char *offset = opt_head + STRING_SIZE("refs/heads/");
+
+               memmove(opt_head, offset, strlen(offset) + 1);
+       }
+
        while (refs_size > 0)
                free(refs[--refs_size].name);
        while (id_refs_size > 0)
@@ -6620,6 +6685,23 @@ load_refs(void)
        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 **))
 {
@@ -6636,16 +6718,57 @@ set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
                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
 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);
 
-       if (!prefixcmp(name, "tig.color."))
+       else if (!strcmp(name, "core.worktree"))
+               set_work_tree(value);
+
+       else if (!prefixcmp(name, "tig.color."))
                set_repo_config_option(name + 10, value, option_color_command);
 
        else if (!prefixcmp(name, "tig.bind."))
@@ -6654,27 +6777,9 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen
        else if (!prefixcmp(name, "tig."))
                set_repo_config_option(name + 4, value, option_set_command);
 
-       /* 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);
-
-       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);
-
-               if (!prefixcmp(value, "refs/heads/")) {
-                       value += STRING_SIZE("refs/heads/");
-                       valuelen -= STRING_SIZE("refs/heads/");
-               }
-
-               if (!string_format_from(opt_remote, &from, "/%s", value))
-                       opt_remote[0] = 0;
-       }
+       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;
 }
@@ -6714,23 +6819,11 @@ read_repo_info(char *name, size_t namelen, char *value, size_t valuelen)
 static int
 load_repo_info(void)
 {
-       const char *head_argv[] = {
-               "git", "symbolic-ref", "HEAD", NULL
-       };
        const char *rev_parse_argv[] = {
                "git", "rev-parse", "--git-dir", "--is-inside-work-tree",
                        "--show-cdup", "--show-prefix", NULL
        };
 
-       if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {
-               chomp_string(opt_head);
-               if (!prefixcmp(opt_head, "refs/heads/")) {
-                       char *offset = opt_head + STRING_SIZE("refs/heads/");
-
-                       memmove(opt_head, offset, strlen(offset) + 1);
-               }
-       }
-
        return run_io_load(rev_parse_argv, "=", read_repo_info);
 }
 
@@ -6876,6 +6969,7 @@ main(int argc, const char *argv[])
        size_t i;
 
        signal(SIGINT, quit);
+       signal(SIGPIPE, SIG_IGN);
 
        if (setlocale(LC_ALL, "")) {
                char *codeset = nl_langinfo(CODESET);
@@ -6930,7 +7024,17 @@ main(int argc, const char *argv[])
                {
                        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;