*/
#ifndef VERSION
-#define VERSION "tig-0.5.git"
+#define VERSION "tig-0.6.git"
#endif
#ifndef DEBUG
#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"
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? */
};
}
BUFPUT('\'');
+ if (bufsize < SIZEOF_STR)
+ buf[bufsize] = 0;
+
return bufsize;
}
/* 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;
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);
die("command too long");
opt_cmd[buf_size] = 0;
-
}
if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
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)
static char buf[BUFSIZ];
static char key_char[] = "'X'";
size_t pos = 0;
- char *sep = " ";
+ char *sep = "";
int i;
buf[pos] = 0;
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", &pager_ops, ""),
+ VIEW_(PAGER, "pager", &pager_ops, ""),
};
#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
}
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;
}
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 {
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. */
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);
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);
}
return FALSE;
}
+static struct line *
+add_line_text(struct view *view, char *data, enum line_type type)
+{
+ struct line *line = &view->line[view->lines];
+
+ if (!data)
+ return NULL;
+
+ line->data = strdup(data);
+ if (!line->data)
+ return NULL;
+
+ line->type = type;
+ view->lines++;
+
+ return line;
+}
+
/*
* View opening
return;
}
- view->ops->read(view, "Quick reference for tig keybindings:");
+ 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) {
- view->ops->read(view, "");
- view->ops->read(view, req_info[i].help);
+ 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))
+ if (!string_format(buf, " %-25s %s", key, req_info[i].help))
continue;
- view->ops->read(view, buf);
+ add_line_text(view, buf, LINE_DEFAULT);
}
}
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 */
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");
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;
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)))
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(view->ref, text);
+ string_copy(ref_commit, 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 ... */
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;
}
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);
} else if (line->type != LINE_TREE_DIR) {
return;
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 = {
;
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, "[");
case LINE_AUTHOR:
{
+ /* Parse author lines where the name may be empty:
+ * author <email@address.tld> 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;
time -= tz;
}
+
gmtime_r(&time, &commit->time);
}
break;
{
struct ref *ref;
bool tag = FALSE;
+ bool remote = FALSE;
if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
/* Commits referenced by tags has "^{}" appended. */
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/");
strncpy(ref->name, name, namelen);
ref->name[namelen] = 0;
ref->tag = tag;
+ ref->remote = remote;
string_copy(ref->id, id);
return OK;