Fix compatibility for git rev-parse without --symbolic-full-name
[tig] / tig.c
diff --git a/tig.c b/tig.c
index ee9d706..a5d6a5e 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -143,11 +143,12 @@ static size_t utf8_length(const char *string, size_t max_width, int *trimmed, bo
 struct ref {
        char *name;             /* Ref name; tag or head names are shortened. */
        char id[SIZEOF_REV];    /* Commit SHA1 ID */
+       unsigned int head:1;    /* Is it the current HEAD? */
        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 tracked:1; /* Is it the remote for the current HEAD? */
        unsigned int next:1;    /* For ref lists: are there more refs? */
-       unsigned int head:1;    /* Is it the current HEAD? */
 };
 
 static struct ref **get_refs(char *id);
@@ -447,6 +448,7 @@ static char opt_path[SIZEOF_STR]    = "";
 static char opt_file[SIZEOF_STR]       = "";
 static char opt_ref[SIZEOF_REF]                = "";
 static char opt_head[SIZEOF_REF]       = "";
+static char opt_remote[SIZEOF_REF]     = "";
 static bool opt_no_head                        = TRUE;
 static FILE *opt_pipe                  = NULL;
 static char opt_encoding[20]           = "UTF-8";
@@ -584,16 +586,18 @@ LINE(DEFAULT,        "",                  COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
 LINE(CURSOR,      "",                  COLOR_WHITE,    COLOR_GREEN,    A_BOLD), \
 LINE(STATUS,      "",                  COLOR_GREEN,    COLOR_DEFAULT,  0), \
 LINE(DELIMITER,           "",                  COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
+LINE(LINE_NUMBER,  "",                 COLOR_CYAN,     COLOR_DEFAULT,  0), \
 LINE(TITLE_BLUR,   "",                 COLOR_WHITE,    COLOR_BLUE,     0), \
 LINE(TITLE_FOCUS,  "",                 COLOR_WHITE,    COLOR_BLUE,     A_BOLD), \
 LINE(MAIN_DATE,    "",                 COLOR_BLUE,     COLOR_DEFAULT,  0), \
 LINE(MAIN_AUTHOR,  "",                 COLOR_GREEN,    COLOR_DEFAULT,  0), \
 LINE(MAIN_COMMIT,  "",                 COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
 LINE(MAIN_TAG,     "",                 COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
-LINE(MAIN_LOCAL_TAG,"",                        COLOR_MAGENTA,  COLOR_DEFAULT,  A_BOLD), \
-LINE(MAIN_REMOTE,  "",                 COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
-LINE(MAIN_REF,     "",                 COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
-LINE(MAIN_HEAD,    "",                 COLOR_RED,      COLOR_DEFAULT,  A_BOLD), \
+LINE(MAIN_LOCAL_TAG,"",                        COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
+LINE(MAIN_REMOTE,  "",                 COLOR_YELLOW,   COLOR_DEFAULT,  0), \
+LINE(MAIN_TRACKED, "",                 COLOR_YELLOW,   COLOR_DEFAULT,  A_BOLD), \
+LINE(MAIN_REF,     "",                 COLOR_CYAN,     COLOR_DEFAULT,  0), \
+LINE(MAIN_HEAD,    "",                 COLOR_CYAN,     COLOR_DEFAULT,  A_BOLD), \
 LINE(MAIN_REVGRAPH,"",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
 LINE(TREE_DIR,     "",                 COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
 LINE(TREE_FILE,    "",                 COLOR_DEFAULT,  COLOR_DEFAULT,  A_NORMAL), \
@@ -606,8 +610,7 @@ LINE(STAT_UNTRACKED,"",                     COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
 LINE(BLAME_DATE,    "",                        COLOR_BLUE,     COLOR_DEFAULT,  0), \
 LINE(BLAME_AUTHOR,  "",                        COLOR_GREEN,    COLOR_DEFAULT,  0), \
 LINE(BLAME_COMMIT, "",                 COLOR_DEFAULT,  COLOR_DEFAULT,  0), \
-LINE(BLAME_ID,     "",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0), \
-LINE(BLAME_LINENO, "",                 COLOR_CYAN,     COLOR_DEFAULT,  0)
+LINE(BLAME_ID,     "",                 COLOR_MAGENTA,  COLOR_DEFAULT,  0)
 
 enum line_type {
 #define LINE(type, line, fg, bg, attr) \
@@ -1504,6 +1507,8 @@ draw_lineno(struct view *view, unsigned int lineno, int max, bool selected)
        if (max < max_number)
                max_number = max;
 
+       if (!selected)
+               wattrset(view->win, get_line_attr(LINE_LINE_NUMBER));
        col = draw_text(view, number, max_number, showtrimmed, selected);
        if (col < max) {
                if (!selected)
@@ -2302,6 +2307,7 @@ enum open_flags {
        OPEN_SPLIT = 1,         /* Split current view. */
        OPEN_BACKGROUNDED = 2,  /* Backgrounded. */
        OPEN_RELOAD = 4,        /* Reload view even if it is the current. */
+       OPEN_NOMAXIMIZE = 8,    /* Do not maximize the current view. */
 };
 
 static void
@@ -2310,6 +2316,7 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
        bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
        bool split = !!(flags & OPEN_SPLIT);
        bool reload = !!(flags & OPEN_RELOAD);
+       bool nomaximize = !!(flags & OPEN_NOMAXIMIZE);
        struct view *view = VIEW(request);
        int nviews = displayed_views();
        struct view *base_view = display[0];
@@ -2328,7 +2335,7 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
                display[1] = view;
                if (!backgrounded)
                        current_view = 1;
-       } else {
+       } else if (!nomaximize) {
                /* Maximize the current view. */
                memset(display, 0, sizeof(display));
                current_view = 0;
@@ -2374,7 +2381,7 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
        if (view->pipe && view->lines == 0) {
                /* Clear the old view and let the incremental updating refill
                 * the screen. */
-               wclear(view->win);
+               werase(view->win);
                report("");
        } else {
                redraw_view(view);
@@ -2501,7 +2508,12 @@ view_driver(struct view *view, enum request request)
 
        if (request > REQ_NONE) {
                open_run_request(request);
-               return TRUE;
+               /* FIXME: When all views can refresh always do this. */
+               if (view == VIEW(REQ_VIEW_STATUS) ||
+                   view == VIEW(REQ_VIEW_STAGE))
+                       request = REQ_REFRESH;
+               else
+                       return TRUE;
        }
 
        if (view && view->lines) {
@@ -2634,27 +2646,27 @@ view_driver(struct view *view, enum request request)
 
        case REQ_TOGGLE_LINENO:
                opt_line_number = !opt_line_number;
-               redraw_display();
+               redraw_view(view);
                break;
 
        case REQ_TOGGLE_DATE:
                opt_date = !opt_date;
-               redraw_display();
+               redraw_view(view);
                break;
 
        case REQ_TOGGLE_AUTHOR:
                opt_author = !opt_author;
-               redraw_display();
+               redraw_view(view);
                break;
 
        case REQ_TOGGLE_REV_GRAPH:
                opt_rev_graph = !opt_rev_graph;
-               redraw_display();
+               redraw_view(view);
                break;
 
        case REQ_TOGGLE_REFS:
                opt_show_refs = !opt_show_refs;
-               redraw_display();
+               redraw_view(view);
                break;
 
        case REQ_PROMPT:
@@ -3476,6 +3488,8 @@ blame_read_file(struct view *view, char *line)
 
                if (view->lines > 0)
                        pipe = popen(view->cmd, "r");
+               else if (!view->parent)
+                       die("No blame exist for %s", view->vid);
                view->cmd[0] = 0;
                if (!pipe) {
                        report("Failed to load blame data");
@@ -3632,8 +3646,6 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno, bool selec
        }
 
        {
-               if (!selected)
-                       wattrset(view->win, get_line_attr(LINE_BLAME_LINENO));
                col += draw_lineno(view, lineno, view->width - col, selected);
                if (col >= view->width)
                        return TRUE;
@@ -4002,6 +4014,8 @@ static bool
 status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
 {
        struct status *status = line->data;
+       char *text;
+       int col = 0;
 
        wmove(view->win, lineno, 0);
 
@@ -4022,8 +4036,6 @@ status_draw(struct view *view, struct line *line, unsigned int lineno, bool sele
        }
 
        if (!status) {
-               char *text;
-
                switch (line->type) {
                case LINE_STAT_STAGED:
                        text = "Changes to be committed:";
@@ -4048,19 +4060,16 @@ status_draw(struct view *view, struct line *line, unsigned int lineno, bool sele
                default:
                        return FALSE;
                }
+       } else {
+               char buf[] = { status->status, ' ', ' ', ' ', 0 };
 
-               draw_text(view, text, view->width, TRUE, selected);
-               return TRUE;
+               col += draw_text(view, buf, view->width, TRUE, selected);
+               if (!selected)
+                       wattrset(view->win, A_NORMAL);
+               text = status->new.name;
        }
 
-       waddch(view->win, status->status);
-       if (!selected)
-               wattrset(view->win, A_NORMAL);
-       wmove(view->win, lineno, 4);
-       if (view->width < 5)
-               return TRUE;
-
-       draw_text(view, status->new.name, view->width - 5, TRUE, selected);
+       draw_text(view, text, view->width - col, TRUE, selected);
        return TRUE;
 }
 
@@ -4159,6 +4168,23 @@ status_enter(struct view *view, struct line *line)
        return REQ_NONE;
 }
 
+static bool
+status_exists(struct status *status, enum line_type type)
+{
+       struct view *view = VIEW(REQ_VIEW_STATUS);
+       struct line *line;
+
+       for (line = view->line; line < view->line + view->lines; line++) {
+               struct status *pos = line->data;
+
+               if (line->type == type && pos &&
+                   !strcmp(status->new.name, pos->new.name))
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
 
 static FILE *
 status_update_prepare(enum line_type type)
@@ -4449,36 +4475,39 @@ stage_diff_line(FILE *pipe, struct line *line)
        return written == bufsize;
 }
 
-static struct line *
-stage_diff_hdr(struct view *view, struct line *line)
+static bool
+stage_diff_write(FILE *pipe, struct line *line, struct line *end)
 {
-       int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
-       struct line *diff_hdr;
-
-       if (line->type == LINE_DIFF_CHUNK)
-               diff_hdr = line - 1;
-       else
-               diff_hdr = view->line + 1;
+       while (line < end) {
+               if (!stage_diff_line(pipe, line++))
+                       return FALSE;
+               if (line->type == LINE_DIFF_CHUNK ||
+                   line->type == LINE_DIFF_HEADER)
+                       break;
+       }
 
-       while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
-               if (diff_hdr->type == LINE_DIFF_HEADER)
-                       return diff_hdr;
+       return TRUE;
+}
 
-               diff_hdr += diff_hdr_dir;
-       }
+static struct line *
+stage_diff_find(struct view *view, struct line *line, enum line_type type)
+{
+       for (; view->line < line; line--)
+               if (line->type == type)
+                       return line;
 
        return NULL;
 }
 
 static bool
-stage_update_chunk(struct view *view, struct line *line)
+stage_update_chunk(struct view *view, struct line *chunk)
 {
        char cmd[SIZEOF_STR];
        size_t cmdsize = 0;
-       struct line *diff_hdr, *diff_chunk, *diff_end;
+       struct line *diff_hdr;
        FILE *pipe;
 
-       diff_hdr = stage_diff_hdr(view, line);
+       diff_hdr = stage_diff_find(view, chunk, LINE_DIFF_HEADER);
        if (!diff_hdr)
                return FALSE;
 
@@ -4496,69 +4525,35 @@ stage_update_chunk(struct view *view, struct line *line)
        if (!pipe)
                return FALSE;
 
-       diff_end = view->line + view->lines;
-       if (line->type != LINE_DIFF_CHUNK) {
-               diff_chunk = diff_hdr;
-
-       } else {
-               for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
-                       if (diff_chunk->type == LINE_DIFF_CHUNK ||
-                           diff_chunk->type == LINE_DIFF_HEADER)
-                               diff_end = diff_chunk;
-
-               diff_chunk = line;
-
-               while (diff_hdr->type != LINE_DIFF_CHUNK) {
-                       switch (diff_hdr->type) {
-                       case LINE_DIFF_HEADER:
-                       case LINE_DIFF_INDEX:
-                       case LINE_DIFF_ADD:
-                       case LINE_DIFF_DEL:
-                               break;
-
-                       default:
-                               diff_hdr++;
-                               continue;
-                       }
-
-                       if (!stage_diff_line(pipe, diff_hdr++)) {
-                               pclose(pipe);
-                               return FALSE;
-                       }
-               }
-       }
-
-       while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
-               diff_chunk++;
+       if (!stage_diff_write(pipe, diff_hdr, chunk) ||
+           !stage_diff_write(pipe, chunk, view->line + view->lines))
+               chunk = NULL;
 
        pclose(pipe);
 
-       if (diff_chunk != diff_end)
-               return FALSE;
-
-       return TRUE;
+       return chunk ? TRUE : FALSE;
 }
 
-static void
+static bool
 stage_update(struct view *view, struct line *line)
 {
-       if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED &&
-           (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
-               if (!stage_update_chunk(view, line)) {
+       struct line *chunk = NULL;
+
+       if (!opt_no_head && stage_line_type != LINE_STAT_UNTRACKED)
+               chunk = stage_diff_find(view, line, LINE_DIFF_CHUNK);
+
+       if (chunk) {
+               if (!stage_update_chunk(view, chunk)) {
                        report("Failed to apply chunk");
-                       return;
+                       return FALSE;
                }
 
        } else if (!status_update_file(&stage_status, stage_line_type)) {
                report("Failed to update file");
-               return;
+               return FALSE;
        }
 
-       open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
-
-       view = VIEW(REQ_VIEW_STATUS);
-       if (view_is_displayed(view))
-               status_enter(view, &view->line[view->lineno]);
+       return TRUE;
 }
 
 static enum request
@@ -4576,6 +4571,10 @@ stage_request(struct view *view, enum request request, struct line *line)
                open_editor(stage_status.status != '?', stage_status.new.name);
                break;
 
+       case REQ_REFRESH:
+               /* Reload everything ... */
+               break;
+
        case REQ_VIEW_BLAME:
                if (stage_status.new.name[0]) {
                        string_copy(opt_file, stage_status.new.name);
@@ -4584,13 +4583,25 @@ stage_request(struct view *view, enum request request, struct line *line)
                return request;
 
        case REQ_ENTER:
-               pager_request(view, request, line);
-               break;
+               return pager_request(view, request, line);
 
        default:
                return request;
        }
 
+       open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD | OPEN_NOMAXIMIZE);
+
+       /* Check whether the staged entry still exists, and close the
+        * stage view if it doesn't. */
+       if (!status_exists(&stage_status, stage_line_type))
+               return REQ_VIEW_CLOSE;
+
+       if (stage_line_type == LINE_STAT_UNTRACKED)
+               opt_pipe = fopen(stage_status.new.name, "r");
+       else
+               string_copy(opt_cmd, view->cmd);
+       open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_NOMAXIMIZE);
+
        return REQ_NONE;
 }
 
@@ -4903,6 +4914,8 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select
                                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]->tracked)
+                               wattrset(view->win, get_line_attr(LINE_MAIN_TRACKED));
                        else if (commit->refs[i]->remote)
                                wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE));
                        else
@@ -4936,6 +4949,8 @@ main_read(struct view *view, char *line)
        struct commit *commit;
 
        if (!line) {
+               if (!view->lines && !view->parent)
+                       die("No revisions match the given arguments.");
                update_rev_graph(graph);
                return TRUE;
        }
@@ -5510,6 +5525,7 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen)
        bool tag = FALSE;
        bool ltag = FALSE;
        bool remote = FALSE;
+       bool tracked = FALSE;
        bool check_replace = FALSE;
        bool head = FALSE;
 
@@ -5531,6 +5547,7 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen)
                remote = TRUE;
                namelen -= STRING_SIZE("refs/remotes/");
                name    += STRING_SIZE("refs/remotes/");
+               tracked  = !strcmp(opt_remote, name);
 
        } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
                namelen -= STRING_SIZE("refs/heads/");
@@ -5563,10 +5580,11 @@ read_ref(char *id, size_t idlen, char *name, size_t namelen)
 
        strncpy(ref->name, name, namelen);
        ref->name[namelen] = 0;
+       ref->head = head;
        ref->tag = tag;
        ref->ltag = ltag;
        ref->remote = remote;
-       ref->head = head;
+       ref->tracked = tracked;
        string_copy_rev(ref->id, id);
 
        return OK;
@@ -5590,11 +5608,33 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen
        if (!strcmp(name, "core.editor"))
                string_ncopy(opt_editor, value, valuelen);
 
+       /* branch.<head>.remote */
+       if (*opt_head &&
+           !strncmp(name, "branch.", 7) &&
+           !strncmp(name + 7, opt_head, strlen(opt_head)) &&
+           !strcmp(name + 7 + strlen(opt_head), ".remote"))
+               string_ncopy(opt_remote, value, valuelen);
+
+       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 (!strncmp(value, "refs/heads/", STRING_SIZE("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;
+       }
+
        return OK;
 }
 
 static int
-load_repo_config(void)
+load_git_config(void)
 {
        return read_properties(popen(GIT_CONFIG " --list", "r"),
                               "=", read_repo_config_option);
@@ -5631,8 +5671,8 @@ static int
 load_repo_info(void)
 {
        int result;
-       FILE *pipe = popen("git rev-parse --git-dir --is-inside-work-tree "
-                          " --show-cdup --symbolic-full-name HEAD 2>/dev/null", "r");
+       FILE *pipe = popen("(git rev-parse --git-dir --is-inside-work-tree "
+                          " --show-cdup; git symbolic-ref HEAD) 2>/dev/null", "r");
 
        /* XXX: The line outputted by "--show-cdup" can be empty so
         * initialize it to something invalid to make it possible to
@@ -5749,9 +5789,7 @@ main(int argc, char *argv[])
        if (load_options() == ERR)
                die("Failed to load user config.");
 
-       /* Load the repo config file so options can be overwritten from
-        * the command line. */
-       if (load_repo_config() == ERR)
+       if (load_git_config() == ERR)
                die("Failed to load repo config.");
 
        if (!parse_options(argc, argv))
@@ -5770,7 +5808,7 @@ main(int argc, char *argv[])
                        die("Failed to initialize character set conversion");
        }
 
-       if (load_refs() == ERR)
+       if (*opt_git_dir && load_refs() == ERR)
                die("Failed to load refs.");
 
        for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)