Simplify subcommand option parsing by moving it out of the loop
[tig] / tig.c
diff --git a/tig.c b/tig.c
index ebeca54..f8132fd 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -58,7 +58,7 @@ static void warn(const char *msg, ...);
 static void report(const char *msg, ...);
 static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
 static void set_nonblocking_input(bool loading);
-static size_t utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed);
+static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve);
 
 #define ABS(x)         ((x) >= 0  ? (x) : -(x))
 #define MIN(x, y)      ((x) < (y) ? (x) :  (y))
@@ -110,13 +110,13 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
        "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
 
 #define TIG_DIFF_CMD \
-       "git show --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
+       "git show --pretty=fuller --no-color --root --patch-with-stat --find-copies-harder -C %s 2>/dev/null"
 
 #define TIG_LOG_CMD    \
        "git log --no-color --cc --stat -n100 %s 2>/dev/null"
 
 #define TIG_MAIN_CMD \
-       "git log --no-color --topo-order --boundary --pretty=raw %s 2>/dev/null"
+       "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
 
 #define TIG_TREE_CMD   \
        "git ls-tree %s %s"
@@ -140,6 +140,7 @@ struct ref {
        char *name;             /* Ref name; tag or head names are shortened. */
        char id[SIZEOF_REV];    /* Commit SHA1 ID */
        unsigned int tag:1;     /* Is it a tag? */
+       unsigned int ltag:1;    /* If so, is the tag local? */
        unsigned int remote:1;  /* Is it a remote ref? */
        unsigned int next:1;    /* For ref lists: are there more refs? */
 };
@@ -354,7 +355,10 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
        REQ_(SHOW_VERSION,      "Show version information"), \
        REQ_(STOP_LOADING,      "Stop all loading views"), \
        REQ_(TOGGLE_LINENO,     "Toggle line numbers"), \
+       REQ_(TOGGLE_DATE,       "Toggle date display"), \
+       REQ_(TOGGLE_AUTHOR,     "Toggle author display"), \
        REQ_(TOGGLE_REV_GRAPH,  "Toggle revision graph visualization"), \
+       REQ_(TOGGLE_REFS,       "Toggle reference display (tags/branches)"), \
        REQ_(STATUS_UPDATE,     "Update file status"), \
        REQ_(STATUS_MERGE,      "Merge file using external tool"), \
        REQ_(TREE_PARENT,       "Switch to parent directory in tree view"), \
@@ -422,8 +426,11 @@ static const char usage[] =
 "  -h, --help      Show help message and exit\n";
 
 /* Option and state variables. */
+static bool opt_date                   = TRUE;
+static bool opt_author                 = TRUE;
 static bool opt_line_number            = FALSE;
 static bool opt_rev_graph              = FALSE;
+static bool opt_show_refs              = TRUE;
 static int opt_num_interval            = NUMBER_INTERVAL;
 static int opt_tab_size                        = TABSIZE;
 static enum request opt_request                = REQ_VIEW_MAIN;
@@ -489,32 +496,33 @@ parse_options(int argc, char *argv[])
 {
        char *altargv[1024];
        int altargc = 0;
-       char *subcommand = NULL;
+       char *subcommand;
        int i;
 
-       for (i = 1; i < argc; i++) {
-               char *opt = argv[i];
+       if (argc <= 1)
+               return TRUE;
 
-               if (!strcmp(opt, "log") ||
-                   !strcmp(opt, "diff")) {
-                       subcommand = opt;
-                       opt_request = opt[0] == 'l'
-                                   ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
-                       warn("`tig %s' has been deprecated", opt);
-                       break;
-               }
+       subcommand = argv[1];
+       if (!strcmp(subcommand, "status")) {
+               opt_request = REQ_VIEW_STATUS;
+               if (argc > 2)
+                       warn("ignoring arguments after `%s'", subcommand);
+               return TRUE;
 
-               if (!strcmp(opt, "show")) {
-                       subcommand = opt;
-                       opt_request = REQ_VIEW_DIFF;
-                       break;
-               }
+       } else if (!strcmp(subcommand, "show")) {
+               opt_request = REQ_VIEW_DIFF;
 
-               if (!strcmp(opt, "status")) {
-                       subcommand = opt;
-                       opt_request = REQ_VIEW_STATUS;
-                       break;
-               }
+       } else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
+               opt_request = subcommand[0] == 'l'
+                           ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
+               warn("`tig %s' has been deprecated", subcommand);
+
+       } else {
+               subcommand = NULL;
+       }
+
+       for (i = 1 + !!subcommand; i < argc; i++) {
+               char *opt = argv[i];
 
                if (opt[0] && opt[0] != '-')
                        break;
@@ -558,18 +566,10 @@ parse_options(int argc, char *argv[])
                warn("`%s' has been deprecated", opt);
        }
 
-       /* Check that no 'alt' arguments occured before a subcommand. */
-       if (subcommand && i < argc && altargc > 0)
-               die("unknown arguments before `%s'", argv[i]);
-
        if (!isatty(STDIN_FILENO)) {
                opt_request = REQ_VIEW_PAGER;
                opt_pipe = stdin;
 
-       } else if (opt_request == REQ_VIEW_STATUS) {
-               if (argc - i > 1)
-                       warn("ignoring arguments after `%s'", argv[i]);
-
        } else if (i < argc || altargc > 0) {
                int alti = 0;
                size_t buf_size;
@@ -577,9 +577,9 @@ parse_options(int argc, char *argv[])
                if (opt_request == REQ_VIEW_MAIN)
                        /* XXX: This is vulnerable to the user overriding
                         * options required for the main view parser. */
-                       string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary");
+                       string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
                else
-                       string_copy(opt_cmd, "git");
+                       string_format(opt_cmd, "git %s", subcommand);
                buf_size = strlen(opt_cmd);
 
                while (buf_size < sizeof(opt_cmd) && alti < altargc) {
@@ -598,9 +598,6 @@ parse_options(int argc, char *argv[])
                opt_cmd[buf_size] = 0;
        }
 
-       if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
-               opt_utf8 = FALSE;
-
        return TRUE;
 }
 
@@ -648,6 +645,7 @@ LINE(MAIN_AUTHOR,  "",                      COLOR_GREEN,    COLOR_DEFAULT,  0), \
 LINE(MAIN_COMMIT,  "",                 COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
 LINE(MAIN_DELIM,   "",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
 LINE(MAIN_TAG,     "",                 COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
+LINE(MAIN_LOCAL_TAG,"",                        COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
 LINE(MAIN_REMOTE,  "",                 COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
 LINE(MAIN_REF,     "",                 COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
 LINE(MAIN_REVGRAPH,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
@@ -808,7 +806,10 @@ static struct keybinding default_keybindings[] = {
        { 'v',          REQ_SHOW_VERSION },
        { 'r',          REQ_SCREEN_REDRAW },
        { '.',          REQ_TOGGLE_LINENO },
+       { 'D',          REQ_TOGGLE_DATE },
+       { 'A',          REQ_TOGGLE_AUTHOR },
        { 'g',          REQ_TOGGLE_REV_GRAPH },
+       { 'F',          REQ_TOGGLE_REFS },
        { ':',          REQ_PROMPT },
        { 'u',          REQ_STATUS_UPDATE },
        { 'M',          REQ_STATUS_MERGE },
@@ -1115,6 +1116,12 @@ option_color_command(int argc, char *argv[])
        return OK;
 }
 
+static bool parse_bool(const char *s)
+{
+       return (!strcmp(s, "1") || !strcmp(s, "true") ||
+               !strcmp(s, "yes")) ? TRUE : FALSE;
+}
+
 /* Wants: name = value */
 static int
 option_set_command(int argc, char *argv[])
@@ -1129,10 +1136,28 @@ option_set_command(int argc, char *argv[])
                return ERR;
        }
 
+       if (!strcmp(argv[0], "show-author")) {
+               opt_author = parse_bool(argv[2]);
+               return OK;
+       }
+
+       if (!strcmp(argv[0], "show-date")) {
+               opt_date = parse_bool(argv[2]);
+               return OK;
+       }
+
        if (!strcmp(argv[0], "show-rev-graph")) {
-               opt_rev_graph = (!strcmp(argv[2], "1") ||
-                                !strcmp(argv[2], "true") ||
-                                !strcmp(argv[2], "yes"));
+               opt_rev_graph = parse_bool(argv[2]);
+               return OK;
+       }
+
+       if (!strcmp(argv[0], "show-refs")) {
+               opt_show_refs = parse_bool(argv[2]);
+               return OK;
+       }
+
+       if (!strcmp(argv[0], "show-line-numbers")) {
+               opt_line_number = parse_bool(argv[2]);
                return OK;
        }
 
@@ -1394,9 +1419,10 @@ struct view {
        struct view *parent;
 
        /* Buffering */
-       unsigned long lines;    /* Total number of lines */
+       size_t lines;           /* Total number of lines */
        struct line *line;      /* Line index */
-       unsigned long line_size;/* Total number of allocated lines */
+       size_t line_alloc;      /* Total number of allocated lines */
+       size_t line_size;       /* Total number of used lines */
        unsigned int digits;    /* Number of digits in the lines member. */
 
        /* Loading */
@@ -1457,47 +1483,37 @@ static struct view views[] = {
        (view == display[0] || view == display[1])
 
 static int
-draw_text(struct view *view, const char *string, int max_len, int col,
+draw_text(struct view *view, const char *string, int max_len,
          bool use_tilde, int tilde_attr)
 {
-       int n;
-
-       n = 0;
-       if (max_len > 0) {
-               int len;
-               int trimmed = FALSE;
+       int len = 0;
+       int trimmed = FALSE;
 
-               if (opt_utf8) {
-                       int pad = 0;
+       if (max_len <= 0)
+               return 0;
 
-                       len = utf8_length(string, max_len, &pad, &trimmed);
-                       if (trimmed && use_tilde) {
+       if (opt_utf8) {
+               len = utf8_length(string, max_len, &trimmed, use_tilde);
+       } else {
+               len = strlen(string);
+               if (len > max_len) {
+                       if (use_tilde) {
                                max_len -= 1;
-                               len = utf8_length(
-                                       string, max_len, &pad, &trimmed);
                        }
-                       n = len;
-               } else {
-                       len = strlen(string);
-                       if (len > max_len) {
-                               if (use_tilde) {
-                                       max_len -= 1;
-                               }
-                               len = max_len;
-                               trimmed = TRUE;
-                       }
-                       n = len;
-               }
-               waddnstr(view->win, string, n);
-               if (trimmed && use_tilde) {
-                       if (tilde_attr != -1)
-                               wattrset(view->win, tilde_attr);
-                       waddch(view->win, '~');
-                       n++;
+                       len = max_len;
+                       trimmed = TRUE;
                }
        }
 
-       return n;
+       waddnstr(view->win, string, len);
+       if (trimmed && use_tilde) {
+               if (tilde_attr != -1)
+                       wattrset(view->win, tilde_attr);
+               waddch(view->win, '~');
+               len++;
+       }
+
+       return len;
 }
 
 static bool
@@ -2076,15 +2092,33 @@ begin_update(struct view *view)
        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)
 {
-       struct line *tmp = realloc(view->line, sizeof(*view->line) * 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;
        view->line_size = line_size;
        return view->line;
 }
@@ -2550,11 +2584,26 @@ view_driver(struct view *view, enum request request)
                redraw_display();
                break;
 
+       case REQ_TOGGLE_DATE:
+               opt_date = !opt_date;
+               redraw_display();
+               break;
+
+       case REQ_TOGGLE_AUTHOR:
+               opt_author = !opt_author;
+               redraw_display();
+               break;
+
        case REQ_TOGGLE_REV_GRAPH:
                opt_rev_graph = !opt_rev_graph;
                redraw_display();
                break;
 
+       case REQ_TOGGLE_REFS:
+               opt_show_refs = !opt_show_refs;
+               redraw_display();
+               break;
+
        case REQ_PROMPT:
                /* Always reload^Wrerun commands from the prompt. */
                open_view(view, opt_request, OPEN_RELOAD);
@@ -2637,7 +2686,6 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selec
 {
        char *text = line->data;
        enum line_type type = line->type;
-       int textlen = strlen(text);
        int attr;
 
        wmove(view->win, lineno, 0);
@@ -2690,13 +2738,9 @@ pager_draw(struct view *view, struct line *line, unsigned int lineno, bool selec
                }
 
        } else {
-               int col = 0, pos = 0;
-
-               for (; pos < textlen && col < view->width; pos++, col++)
-                       if (text[pos] == '\t')
-                               col += TABSIZE - (col % TABSIZE) - 1;
+               int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
 
-               waddnstr(view->win, text, pos);
+               draw_text(view, text, view->width, TRUE, tilde_attr);
        }
 
        return TRUE;
@@ -3371,7 +3415,7 @@ error_out:
 
 /* Don't show unmerged entries in the staged section. */
 #define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
-#define STATUS_DIFF_FILES_CMD "git update-index -q --refresh && git diff-files -z"
+#define STATUS_DIFF_FILES_CMD "git diff-files -z"
 #define STATUS_LIST_OTHER_CMD \
        "git ls-files -z --others --exclude-per-directory=.gitignore"
 
@@ -3396,7 +3440,7 @@ status_open(struct view *view)
        for (i = 0; i < view->lines; i++)
                free(view->line[i].data);
        free(view->line);
-       view->lines = view->line_size = view->lineno = 0;
+       view->lines = view->line_alloc = view->line_size = view->lineno = 0;
        view->line = NULL;
 
        if (!realloc_lines(view, view->line_size + 6))
@@ -3415,6 +3459,8 @@ status_open(struct view *view)
                        return FALSE;
        }
 
+       system("git update-index -q --refresh");
+
        if (!status_run(view, STATUS_DIFF_INDEX_CMD, TRUE, LINE_STAT_STAGED) ||
            !status_run(view, STATUS_DIFF_FILES_CMD, TRUE, LINE_STAT_UNSTAGED) ||
            !status_run(view, cmd, FALSE, LINE_STAT_UNTRACKED))
@@ -3434,12 +3480,14 @@ static bool
 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
 {
        struct status *status = line->data;
+       int tilde_attr = get_line_attr(LINE_MAIN_DELIM);
 
        wmove(view->win, lineno, 0);
 
        if (selected) {
                wattrset(view->win, get_line_attr(LINE_CURSOR));
                wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
+               tilde_attr = -1;
 
        } else if (!status && line->type != LINE_STAT_NONE) {
                wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
@@ -3473,7 +3521,7 @@ status_draw(struct view *view, struct line *line, unsigned int lineno, bool sele
                        return FALSE;
                }
 
-               waddstr(view->win, text);
+               draw_text(view, text, view->width, TRUE, tilde_attr);
                return TRUE;
        }
 
@@ -3481,8 +3529,10 @@ status_draw(struct view *view, struct line *line, unsigned int lineno, bool sele
        if (!selected)
                wattrset(view->win, A_NORMAL);
        wmove(view->win, lineno, 4);
-       waddstr(view->win, status->new.name);
+       if (view->width < 5)
+               return TRUE;
 
+       draw_text(view, status->new.name, view->width - 5, TRUE, tilde_attr);
        return TRUE;
 }
 
@@ -3959,6 +4009,7 @@ struct commit {
        struct ref **refs;              /* Repository references. */
        chtype graph[SIZEOF_REVGRAPH];  /* Ancestry chain graphics. */
        size_t graph_size;              /* The width of the graph array. */
+       bool has_parents;               /* Rewritten --parents seen. */
 };
 
 /* Size of rev graph with no  "padding" columns */
@@ -4184,15 +4235,12 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select
                tilde_attr = get_line_attr(LINE_MAIN_DELIM);
        }
 
-       {
+       if (opt_date) {
                int n;
 
                timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
-               n = draw_text(
-                       view, buf, view->width - col, col, FALSE, tilde_attr);
-               draw_text(
-                       view, " ", view->width - col - n, col + n, FALSE,
-                       tilde_attr);
+               n = draw_text(view, buf, view->width - col, FALSE, tilde_attr);
+               draw_text(view, " ", view->width - col - n, FALSE, tilde_attr);
 
                col += DATE_COLS;
                wmove(view->win, lineno, col);
@@ -4202,46 +4250,50 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select
        if (type != LINE_CURSOR)
                wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
 
-       {
+       if (opt_author) {
                int max_len;
 
                max_len = view->width - col;
                if (max_len > AUTHOR_COLS - 1)
                        max_len = AUTHOR_COLS - 1;
-               draw_text(
-                       view, commit->author, max_len, col, TRUE, tilde_attr);
+               draw_text(view, commit->author, max_len, TRUE, tilde_attr);
                col += AUTHOR_COLS;
                if (col >= view->width)
                        return TRUE;
        }
 
        if (opt_rev_graph && commit->graph_size) {
+               size_t graph_size = view->width - col;
                size_t i;
 
                if (type != LINE_CURSOR)
                        wattrset(view->win, get_line_attr(LINE_MAIN_REVGRAPH));
                wmove(view->win, lineno, col);
+               if (graph_size > commit->graph_size)
+                       graph_size = commit->graph_size;
                /* Using waddch() instead of waddnstr() ensures that
                 * they'll be rendered correctly for the cursor line. */
-               for (i = 0; i < commit->graph_size; i++)
+               for (i = 0; i < graph_size; i++)
                        waddch(view->win, commit->graph[i]);
 
-               waddch(view->win, ' ');
                col += commit->graph_size + 1;
                if (col >= view->width)
                        return TRUE;
+               waddch(view->win, ' ');
        }
        if (type != LINE_CURSOR)
                wattrset(view->win, A_NORMAL);
 
        wmove(view->win, lineno, col);
 
-       if (commit->refs) {
+       if (opt_show_refs && commit->refs) {
                size_t i = 0;
 
                do {
                        if (type == LINE_CURSOR)
                                ;
+                       else if (commit->refs[i]->ltag)
+                               wattrset(view->win, get_line_attr(LINE_MAIN_LOCAL_TAG));
                        else if (commit->refs[i]->tag)
                                wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
                        else if (commit->refs[i]->remote)
@@ -4249,20 +4301,13 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select
                        else
                                wattrset(view->win, get_line_attr(LINE_MAIN_REF));
 
-                       col += draw_text(
-                               view, "[", view->width - col, col, TRUE,
-                               tilde_attr);
-                       col += draw_text(
-                               view, commit->refs[i]->name, view->width - col,
-                               col, TRUE, tilde_attr);
-                       col += draw_text(
-                               view, "]", view->width - col, col, TRUE,
-                               tilde_attr);
+                       col += draw_text(view, "[", view->width - col, TRUE, tilde_attr);
+                       col += draw_text(view, commit->refs[i]->name, view->width - col,
+                                        TRUE, tilde_attr);
+                       col += draw_text(view, "]", view->width - col, TRUE, tilde_attr);
                        if (type != LINE_CURSOR)
                                wattrset(view->win, A_NORMAL);
-                       col += draw_text(
-                               view, " ", view->width - col, col, TRUE,
-                               tilde_attr);
+                       col += draw_text(view, " ", view->width - col, TRUE, tilde_attr);
                        if (col >= view->width)
                                return TRUE;
                } while (commit->refs[i++]->next);
@@ -4271,9 +4316,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select
        if (type != LINE_CURSOR)
                wattrset(view->win, get_line_attr(type));
 
-       col += draw_text(
-               view, commit->title, view->width - col, col, TRUE, tilde_attr);
-
+       draw_text(view, commit->title, view->width - col, TRUE, tilde_attr);
        return TRUE;
 }
 
@@ -4306,6 +4349,12 @@ main_read(struct view *view, char *line)
                commit->refs = get_refs(commit->id);
                graph->commit = commit;
                add_line_data(view, commit, LINE_MAIN_COMMIT);
+
+               while ((line = strchr(line, ' '))) {
+                       line++;
+                       push_rev_graph(graph->parents, line);
+                       commit->has_parents = TRUE;
+               }
                return TRUE;
        }
 
@@ -4315,6 +4364,8 @@ main_read(struct view *view, char *line)
 
        switch (type) {
        case LINE_PARENT:
+               if (commit->has_parents)
+                       break;
                push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
                break;
 
@@ -4487,6 +4538,9 @@ unicode_width(unsigned long c)
            || (c >= 0x30000 && c <= 0x3fffd)))
                return 2;
 
+       if (c == '\t')
+               return opt_tab_size;
+
        return 1;
 }
 
@@ -4554,19 +4608,16 @@ utf8_to_unicode(const char *string, size_t length)
 
 /* Calculates how much of string can be shown within the given maximum width
  * and sets trimmed parameter to non-zero value if all of string could not be
- * shown.
- *
- * Additionally, adds to coloffset how many many columns to move to align with
- * the expected position. Takes into account how multi-byte and double-width
- * characters will effect the cursor position.
+ * shown. If the reserve flag is TRUE, it will reserve at least one
+ * trailing character, which can be useful when drawing a delimiter.
  *
  * Returns the number of bytes to output from string to satisfy max_width. */
 static size_t
-utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
+utf8_length(const char *string, size_t max_width, int *trimmed, bool reserve)
 {
        const char *start = string;
        const char *end = strchr(string, '\0');
-       size_t mbwidth = 0;
+       unsigned char last_bytes = 0;
        size_t width = 0;
 
        *trimmed = 0;
@@ -4592,27 +4643,16 @@ utf8_length(const char *string, size_t max_width, int *coloffset, int *trimmed)
                width  += ucwidth;
                if (width > max_width) {
                        *trimmed = 1;
+                       if (reserve && width - ucwidth == max_width) {
+                               string -= last_bytes;
+                       }
                        break;
                }
 
-               /* The column offset collects the differences between the
-                * number of bytes encoding a character and the number of
-                * columns will be used for rendering said character.
-                *
-                * So if some character A is encoded in 2 bytes, but will be
-                * represented on the screen using only 1 byte this will and up
-                * adding 1 to the multi-byte column offset.
-                *
-                * Assumes that no double-width character can be encoding in
-                * less than two bytes. */
-               if (bytes > ucwidth)
-                       mbwidth += bytes - ucwidth;
-
                string  += bytes;
+               last_bytes = bytes;
        }
 
-       *coloffset += mbwidth;
-
        return string - start;
 }
 
@@ -4795,18 +4835,21 @@ read_prompt(const char *prompt)
  * Repository references
  */
 
-static struct ref *refs;
-static size_t refs_size;
+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;
-static size_t id_refs_size;
+static struct ref ***id_refs = NULL;
+static size_t id_refs_alloc = 0;
+static size_t id_refs_size = 0;
 
 static struct ref **
 get_refs(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;
 
@@ -4814,7 +4857,8 @@ get_refs(char *id)
                if (!strcmp(id, id_refs[i][0]->id))
                        return id_refs[i];
 
-       tmp_id_refs = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
+       tmp_id_refs = realloc_items(id_refs, &id_refs_alloc, id_refs_size + 1,
+                                   sizeof(*id_refs));
        if (!tmp_id_refs)
                return NULL;
 
@@ -4826,7 +4870,8 @@ get_refs(char *id)
                if (strcmp(id, refs[i].id))
                        continue;
 
-               tmp = realloc(ref_list, (ref_list_size + 1) * sizeof(*ref_list));
+               tmp = realloc_items(ref_list, &ref_list_alloc,
+                                   ref_list_size + 1, sizeof(*ref_list));
                if (!tmp) {
                        if (ref_list)
                                free(ref_list);
@@ -4856,15 +4901,19 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen)
 {
        struct ref *ref;
        bool tag = FALSE;
+       bool ltag = FALSE;
        bool remote = FALSE;
+       bool check_replace = FALSE;
 
        if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
-               /* Commits referenced by tags has "^{}" appended. */
-               if (name[namelen - 1] != '}')
-                       return OK;
-
-               while (namelen > 0 && name[namelen] != '^')
-                       namelen--;
+               if (!strcmp(name + namelen - 3, "^{}")) {
+                       namelen -= 3;
+                       name[namelen] = 0;
+                       if (refs_size > 0 && refs[refs_size - 1].ltag == TRUE)
+                               check_replace = TRUE;
+               } else {
+                       ltag = TRUE;
+               }
 
                tag = TRUE;
                namelen -= STRING_SIZE("refs/tags/");
@@ -4883,7 +4932,17 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen)
                return OK;
        }
 
-       refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
+       if (check_replace && !strcmp(name, refs[refs_size - 1].name)) {
+               /* 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 beofre the commit id
+                * it points to. */
+               refs[refs_size - 1].ltag = ltag;
+               string_copy_rev(refs[refs_size - 1].id, id);
+
+               return OK;
+       }
+       refs = realloc_items(refs, &refs_alloc, refs_size + 1, sizeof(*refs));
        if (!refs)
                return ERR;
 
@@ -4895,6 +4954,7 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen)
        strncpy(ref->name, name, namelen);
        ref->name[namelen] = 0;
        ref->tag = tag;
+       ref->ltag = ltag;
        ref->remote = remote;
        string_copy_rev(ref->id, id);
 
@@ -5074,6 +5134,9 @@ main(int argc, char *argv[])
        if (!opt_git_dir[0])
                die("Not a git repository");
 
+       if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
+               opt_utf8 = FALSE;
+
        if (*opt_codeset && strcmp(opt_codeset, opt_encoding)) {
                opt_iconv = iconv_open(opt_codeset, opt_encoding);
                if (opt_iconv == ICONV_NONE)