X-Git-Url: https://git.distorted.org.uk/~mdw/tig/blobdiff_plain/158a522c5740123fd1144f9045ef0a419dfcf090..f098944b7ed412914f2a6f095d0d454d2102c62b:/tig.c diff --git a/tig.c b/tig.c index 1983375..be4b80c 100644 --- a/tig.c +++ b/tig.c @@ -12,7 +12,7 @@ */ #ifndef VERSION -#define VERSION "tig-0.5.git" +#define VERSION "tig-0.6.git" #endif #ifndef DEBUG @@ -90,7 +90,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset, #define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3) #define TIG_LS_REMOTE \ - "git ls-remote . 2>/dev/null" + "git ls-remote $(git rev-parse --git-dir) 2>/dev/null" #define TIG_DIFF_CMD \ "git show --root --patch-with-stat --find-copies-harder -B -C %s 2>/dev/null" @@ -121,6 +121,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 remote:1; /* Is it a remote ref? */ unsigned int next:1; /* For ref lists: are there more refs? */ }; @@ -172,6 +173,9 @@ string_ncopy_do(char *dst, size_t dstlen, const char *src, size_t srclen) #define string_ncopy(dst, src, srclen) \ string_ncopy_do(dst, sizeof(dst), src, srclen) +#define string_copy_rev(dst, src) \ + string_ncopy_do(dst, SIZEOF_REV, src, SIZEOF_REV - 1) + static char * chomp_string(char *name) { @@ -268,6 +272,9 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src) } BUFPUT('\''); + if (bufsize < SIZEOF_STR) + buf[bufsize] = 0; + return bufsize; } @@ -395,7 +402,7 @@ VERSION " (" __DATE__ ")\n" /* Option and state variables. */ static bool opt_line_number = FALSE; -static bool opt_rev_graph = TRUE; +static bool opt_rev_graph = FALSE; static int opt_num_interval = NUMBER_INTERVAL; static int opt_tab_size = TABSIZE; static enum request opt_request = REQ_VIEW_MAIN; @@ -519,7 +526,7 @@ 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 --stat --pretty=raw"); + string_copy(opt_cmd, "git log --pretty=raw"); else string_copy(opt_cmd, "git"); buf_size = strlen(opt_cmd); @@ -533,7 +540,6 @@ parse_options(int argc, char *argv[]) die("command too long"); opt_cmd[buf_size] = 0; - } if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8")) @@ -586,6 +592,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_REMOTE, "", COLOR_YELLOW, COLOR_DEFAULT, A_BOLD), \ LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD), \ LINE(TREE_DIR, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \ LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL) @@ -866,7 +873,7 @@ get_key(enum request request) static char buf[BUFSIZ]; static char key_char[] = "'X'"; size_t pos = 0; - char *sep = " "; + char *sep = ""; int i; buf[pos] = 0; @@ -1227,10 +1234,12 @@ struct view { struct view_ops { /* What type of content being displayed. Used in the title bar. */ const char *type; - /* Draw one line; @lineno must be < view->height. */ - bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected); + /* Open and reads in all view content. */ + bool (*open)(struct view *view); /* Read one line; updates view->line. */ bool (*read)(struct view *view, char *data); + /* Draw one line; @lineno must be < view->height. */ + bool (*draw)(struct view *view, struct line *line, unsigned int lineno, bool selected); /* Depending on view, change display based on current line. */ bool (*enter)(struct view *view, struct line *line); /* Search for regex in a line. */ @@ -1243,6 +1252,7 @@ static struct view_ops pager_ops; static struct view_ops main_ops; static struct view_ops tree_ops; static struct view_ops blob_ops; +static struct view_ops help_ops; #define VIEW_STR(name, cmd, env, ref, ops, map) \ { name, cmd, #env, ref, ops, map} @@ -1257,8 +1267,8 @@ static struct view views[] = { VIEW_(LOG, "log", &pager_ops, ref_head), VIEW_(TREE, "tree", &tree_ops, ref_commit), VIEW_(BLOB, "blob", &blob_ops, ref_blob), - VIEW_(HELP, "help", &pager_ops, "static"), - VIEW_(PAGER, "pager", &pager_ops, "static"), + VIEW_(HELP, "help", &help_ops, ""), + VIEW_(PAGER, "pager", &pager_ops, ""), }; #define VIEW(req) (&views[(req) - REQ_OFFSET - 1]) @@ -1621,7 +1631,8 @@ move_view(struct view *view, enum request request) } if (!view_is_displayed(view)) { - view->offset += steps; + view->offset += scroll_steps; + assert(0 <= view->offset && view->offset < view->lines); view->ops->select(view, &view->line[view->lineno]); return; } @@ -1778,17 +1789,21 @@ begin_update(struct view *view) if (opt_cmd[0]) { string_copy(view->cmd, opt_cmd); opt_cmd[0] = 0; - /* When running random commands, the view ref could have become - * invalid so clear it. */ - view->ref[0] = 0; + /* When running random commands, initially show the + * command in the title. However, it maybe later be + * overwritten if a commit line is selected. */ + string_copy(view->ref, view->cmd); } else if (view == VIEW(REQ_VIEW_TREE)) { const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt; + char path[SIZEOF_STR]; if (strcmp(view->vid, view->id)) - opt_path[0] = 0; + opt_path[0] = path[0] = 0; + else if (sq_quote(path, 0, opt_path) >= sizeof(path)) + return FALSE; - if (!string_format(view->cmd, format, id, opt_path)) + if (!string_format(view->cmd, format, id, path)) return FALSE; } else { @@ -1796,6 +1811,12 @@ begin_update(struct view *view) if (!string_format(view->cmd, format, id, id, id, id, id)) return FALSE; + + /* Put the current ref_* value to the view title ref + * member. This is needed by the blob view. Most other + * views sets it automatically after loading because the + * first line is a commit line. */ + string_copy(view->ref, id); } /* Special case for the pager view. */ @@ -1814,7 +1835,7 @@ begin_update(struct view *view) view->offset = 0; view->lines = 0; view->lineno = 0; - string_copy(view->vid, id); + string_copy_rev(view->vid, id); if (view->line) { int i; @@ -1883,7 +1904,7 @@ update_view(struct view *view) size_t ret; - ret = iconv(opt_iconv, (const char **) &inbuf, &inlen, &outbuf, &outlen); + ret = iconv(opt_iconv, &inbuf, &inlen, &outbuf, &outlen); if (ret != (size_t) -1) { line = out_buffer; linelen = strlen(out_buffer); @@ -1926,6 +1947,12 @@ update_view(struct view *view) if (redraw_from > 0) redraw_from--; + /* Since revision graph visualization requires knowledge + * about the parent commit, it causes a further one-off + * needed to be redrawn for incremental updates. */ + if (redraw_from > 0 && opt_rev_graph) + redraw_from--; + /* Incrementally draw avoids flickering. */ redraw_view_from(view, redraw_from); } @@ -1955,48 +1982,28 @@ end: return FALSE; } - -/* - * View opening - */ - -static void open_help_view(struct view *view) +static struct line * +add_line_text(struct view *view, char *data, enum line_type type) { - char buf[BUFSIZ]; - int lines = ARRAY_SIZE(req_info) + 2; - int i; - - if (view->lines > 0) - return; - - for (i = 0; i < ARRAY_SIZE(req_info); i++) - if (!req_info[i].request) - lines++; + struct line *line = &view->line[view->lines]; - view->line = calloc(lines, sizeof(*view->line)); - if (!view->line) { - report("Allocation failure"); - return; - } + if (!data) + return NULL; - view->ops->read(view, "Quick reference for tig keybindings:"); + line->data = strdup(data); + if (!line->data) + return NULL; - for (i = 0; i < ARRAY_SIZE(req_info); i++) { - char *key; + line->type = type; + view->lines++; - if (!req_info[i].request) { - view->ops->read(view, ""); - view->ops->read(view, req_info[i].help); - continue; - } + return line; +} - key = get_key(req_info[i].request); - if (!string_format(buf, "%-25s %s", key, req_info[i].help)) - continue; - view->ops->read(view, buf); - } -} +/* + * View opening + */ enum open_flags { OPEN_DEFAULT = 0, /* Use default view switching. */ @@ -2020,8 +2027,11 @@ open_view(struct view *prev, enum request request, enum open_flags flags) return; } - if (view == VIEW(REQ_VIEW_HELP)) { - open_help_view(view); + if (view->ops->open) { + if (!view->ops->open(view)) { + report("Failed to load %s view", view->name); + return; + } } else if ((reload || strcmp(view->vid, view->id)) && !begin_update(view)) { @@ -2109,16 +2119,27 @@ view_driver(struct view *view, enum request request) case REQ_VIEW_BLOB: if (!ref_blob[0]) { - report("No file chosen, press 't' to open tree view"); + report("No file chosen, press %s to open tree view", + get_key(REQ_VIEW_TREE)); break; } - /* Fall-through */ + open_view(view, request, OPEN_DEFAULT); + break; + + case REQ_VIEW_PAGER: + if (!VIEW(REQ_VIEW_PAGER)->lines) { + report("No pager content, press %s to run command from prompt", + get_key(REQ_PROMPT)); + break; + } + open_view(view, request, OPEN_DEFAULT); + break; + case REQ_VIEW_MAIN: case REQ_VIEW_DIFF: case REQ_VIEW_LOG: case REQ_VIEW_TREE: case REQ_VIEW_HELP: - case REQ_VIEW_PAGER: open_view(view, request, OPEN_DEFAULT); break; @@ -2321,7 +2342,7 @@ add_describe_ref(char *buf, size_t *bufpos, char *commit_id, const char *sep) char *ref = NULL; FILE *pipe; - if (!string_format(refbuf, "git describe %s", commit_id)) + if (!string_format(refbuf, "git describe %s 2>/dev/null", commit_id)) return TRUE; pipe = popen(refbuf, "r"); @@ -2363,7 +2384,8 @@ add_pager_refs(struct view *view, struct line *line) do { struct ref *ref = refs[refpos]; - char *fmt = ref->tag ? "%s[%s]" : "%s%s"; + char *fmt = ref->tag ? "%s[%s]" : + ref->remote ? "%s<%s>" : "%s%s"; if (!string_format_from(buf, &bufpos, fmt, sep, ref->name)) return; @@ -2385,30 +2407,21 @@ try_add_describe_ref: if (!realloc_lines(view, view->line_size + 1)) return; - line = &view->line[view->lines]; - line->data = strdup(buf); - if (!line->data) - return; - - line->type = LINE_PP_REFS; - view->lines++; + add_line_text(view, buf, LINE_PP_REFS); } static bool pager_read(struct view *view, char *data) { - struct line *line = &view->line[view->lines]; + struct line *line; if (!data) return TRUE; - line->data = strdup(data); - if (!line->data) + line = add_line_text(view, data, get_line_type(data)); + if (!line) return FALSE; - line->type = get_line_type(line->data); - view->lines++; - if (line->type == LINE_COMMIT && (view == VIEW(REQ_VIEW_DIFF) || view == VIEW(REQ_VIEW_LOG))) @@ -2462,17 +2475,73 @@ static void pager_select(struct view *view, struct line *line) { if (line->type == LINE_COMMIT) { - char *text = line->data; + char *text = line->data + STRING_SIZE("commit "); - string_copy(view->ref, text + STRING_SIZE("commit ")); - string_copy(ref_commit, view->ref); + if (view != VIEW(REQ_VIEW_PAGER)) + string_copy_rev(view->ref, text); + string_copy_rev(ref_commit, text); } } static struct view_ops pager_ops = { "line", - pager_draw, + NULL, pager_read, + pager_draw, + pager_enter, + pager_grep, + pager_select, +}; + + +/* + * Help backend + */ + +static bool +help_open(struct view *view) +{ + char buf[BUFSIZ]; + int lines = ARRAY_SIZE(req_info) + 2; + int i; + + if (view->lines > 0) + return TRUE; + + for (i = 0; i < ARRAY_SIZE(req_info); i++) + if (!req_info[i].request) + lines++; + + view->line = calloc(lines, sizeof(*view->line)); + if (!view->line) + return FALSE; + + add_line_text(view, "Quick reference for tig keybindings:", LINE_DEFAULT); + + for (i = 0; i < ARRAY_SIZE(req_info); i++) { + char *key; + + if (!req_info[i].request) { + add_line_text(view, "", LINE_DEFAULT); + add_line_text(view, req_info[i].help, LINE_DEFAULT); + continue; + } + + key = get_key(req_info[i].request); + if (!string_format(buf, " %-25s %s", key, req_info[i].help)) + continue; + + add_line_text(view, buf, LINE_DEFAULT); + } + + return TRUE; +} + +static struct view_ops help_ops = { + "line", + help_open, + NULL, + pager_draw, pager_enter, pager_grep, pager_select, @@ -2526,21 +2595,18 @@ tree_read(struct view *view, char *text) if (first_read) { /* Add path info line */ - if (string_format(buf, "Directory path /%s", opt_path) && - realloc_lines(view, view->line_size + 1) && - pager_read(view, buf)) - view->line[view->lines - 1].type = LINE_DEFAULT; - else + if (!string_format(buf, "Directory path /%s", opt_path) || + !realloc_lines(view, view->line_size + 1) || + !add_line_text(view, buf, LINE_DEFAULT)) return FALSE; /* Insert "link" to parent directory. */ - if (*opt_path && - string_format(buf, TREE_UP_FORMAT, view->ref) && - realloc_lines(view, view->line_size + 1) && - pager_read(view, buf)) - view->line[view->lines - 1].type = LINE_TREE_DIR; - else if (*opt_path) - return FALSE; + if (*opt_path) { + if (!string_format(buf, TREE_UP_FORMAT, view->ref) || + !realloc_lines(view, view->line_size + 1) || + !add_line_text(view, buf, LINE_TREE_DIR)) + return FALSE; + } } /* Strip the path part ... */ @@ -2579,14 +2645,13 @@ tree_read(struct view *view, char *text) return TRUE; } - if (!pager_read(view, text)) + if (!add_line_text(view, text, type)) return FALSE; /* Move the current line to the first tree entry. */ if (first_read) view->lineno++; - view->line[view->lines - 1].type = type; return TRUE; } @@ -2644,27 +2709,23 @@ tree_enter(struct view *view, struct line *line) static void tree_select(struct view *view, struct line *line) { - char *text = line->data; - - text += STRING_SIZE("100644 blob "); + char *text = line->data + STRING_SIZE("100644 blob "); if (line->type == LINE_TREE_FILE) { - string_ncopy(ref_blob, text, 40); - /* Also update the blob view's ref, since all there must always - * be in sync. */ - string_copy(VIEW(REQ_VIEW_BLOB)->ref, ref_blob); + string_copy_rev(ref_blob, text); } else if (line->type != LINE_TREE_DIR) { return; } - string_ncopy(view->ref, text, 40); + string_copy_rev(view->ref, text); } static struct view_ops tree_ops = { "file", - pager_draw, + NULL, tree_read, + pager_draw, tree_enter, pager_grep, tree_select, @@ -2673,18 +2734,14 @@ static struct view_ops tree_ops = { static bool blob_read(struct view *view, char *line) { - bool state = pager_read(view, line); - - if (state == TRUE) - view->line[view->lines - 1].type = LINE_DEFAULT; - - return state; + return add_line_text(view, line, LINE_DEFAULT); } static struct view_ops blob_ops = { "line", - pager_draw, + NULL, blob_read, + pager_draw, pager_enter, pager_grep, pager_select, @@ -2978,6 +3035,8 @@ main_draw(struct view *view, struct line *line, unsigned int lineno, bool select ; else if (commit->refs[i]->tag) wattrset(view->win, get_line_attr(LINE_MAIN_TAG)); + else if (commit->refs[i]->remote) + wattrset(view->win, get_line_attr(LINE_MAIN_REMOTE)); else wattrset(view->win, get_line_attr(LINE_MAIN_REF)); waddstr(view->win, "["); @@ -3030,7 +3089,7 @@ main_read(struct view *view, char *line) line += STRING_SIZE("commit "); view->line[view->lines++].data = commit; - string_copy(commit->id, line); + string_copy_rev(commit->id, line); commit->refs = get_refs(commit->id); graph->commit = commit; break; @@ -3044,46 +3103,35 @@ main_read(struct view *view, char *line) case LINE_AUTHOR: { + /* Parse author lines where the name may be empty: + * author 1138474660 +0100 + */ char *ident = line + STRING_SIZE("author "); - char *end = strchr(ident, '<'); + char *nameend = strchr(ident, '<'); + char *emailend = strchr(ident, '>'); - if (!commit) + if (!commit || !nameend || !emailend) break; update_rev_graph(graph); graph = graph->next; - if (end) { - char *email = end + 1; - - for (; end > ident && isspace(end[-1]); end--) ; - - if (end == ident && *email) { - ident = email; - end = strchr(ident, '>'); - for (; end > ident && isspace(end[-1]); end--) ; - } - *end = 0; + *nameend = *emailend = 0; + ident = chomp_string(ident); + if (!*ident) { + ident = chomp_string(nameend + 1); + if (!*ident) + ident = "Unknown"; } - /* End is NULL or ident meaning there's no author. */ - if (end <= ident) - ident = "Unknown"; - string_copy(commit->author, ident); /* Parse epoch and timezone */ - if (end) { - char *secs = strchr(end + 1, '>'); - char *zone; - time_t time; - - if (!secs || secs[1] != ' ') - break; + if (emailend[1] == ' ') { + char *secs = emailend + 2; + char *zone = strchr(secs, ' '); + time_t time = (time_t) atol(secs); - secs += 2; - time = (time_t) atol(secs); - zone = strchr(secs, ' '); if (zone && strlen(zone) == STRING_SIZE(" +0700")) { long tz; @@ -3098,6 +3146,7 @@ main_read(struct view *view, char *line) time -= tz; } + gmtime_r(&time, &commit->time); } break; @@ -3175,14 +3224,15 @@ main_select(struct view *view, struct line *line) { struct commit *commit = line->data; - string_copy(view->ref, commit->id); - string_copy(ref_commit, view->ref); + string_copy_rev(view->ref, commit->id); + string_copy_rev(ref_commit, view->ref); } static struct view_ops main_ops = { "commit", - main_draw, + NULL, main_read, + main_draw, main_enter, main_grep, main_select, @@ -3573,6 +3623,7 @@ read_ref(char *id, int idlen, char *name, int namelen) { struct ref *ref; bool tag = FALSE; + bool remote = FALSE; if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) { /* Commits referenced by tags has "^{}" appended. */ @@ -3586,6 +3637,11 @@ read_ref(char *id, int idlen, char *name, int namelen) namelen -= STRING_SIZE("refs/tags/"); name += STRING_SIZE("refs/tags/"); + } else if (!strncmp(name, "refs/remotes/", STRING_SIZE("refs/remotes/"))) { + remote = TRUE; + namelen -= STRING_SIZE("refs/remotes/"); + name += STRING_SIZE("refs/remotes/"); + } else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) { namelen -= STRING_SIZE("refs/heads/"); name += STRING_SIZE("refs/heads/"); @@ -3606,7 +3662,8 @@ read_ref(char *id, int idlen, char *name, int namelen) strncpy(ref->name, name, namelen); ref->name[namelen] = 0; ref->tag = tag; - string_copy(ref->id, id); + ref->remote = remote; + string_copy_rev(ref->id, id); return OK; }