index d1d63da05b3b6c30449ec54058d97d1d0fdf7652..e918fe63900d592799dec3bcd85eb2ac72909662 100644 (file)
--- a/tig.c
+++ b/tig.c
* GNU General Public License for more details.
*/
-#ifndef VERSION
-#define VERSION "tig-0.4.git"
+#ifndef VERSION
+#define VERSION "unknown-version"
#endif
#ifndef DEBUG
#define SIZEOF_REVGRAPH 19 /* Size of revision ancestry graphics. */
-/* Size of rev graph with no "padding" columns */
-#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
-
/* This color name can be used to refer to the default term colors. */
#define COLOR_DEFAULT (-1)
#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"
/* XXX: Needs to be defined to the empty string. */
#define TIG_HELP_CMD ""
#define TIG_PAGER_CMD ""
+#define TIG_STATUS_CMD ""
/* Some ascii-shorthands fitted into the ncurses namespace. */
#define KEY_TAB '\t'
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? */
};
#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)
{
}
BUFPUT('\'');
+ if (bufsize < SIZEOF_STR)
+ buf[bufsize] = 0;
+
return bufsize;
}
REQ_(VIEW_BLOB, "Show blob view"), \
REQ_(VIEW_HELP, "Show help page"), \
REQ_(VIEW_PAGER, "Show pager view"), \
+ REQ_(VIEW_STATUS, "Show status view"), \
\
REQ_GROUP("View manipulation") \
REQ_(ENTER, "Enter current line and scroll"), \
*/
static const char usage[] =
-VERSION " (" __DATE__ ")\n"
+"tig " VERSION " (" __DATE__ ")\n"
"\n"
"Usage: tig [options]\n"
" or: tig [options] [--] [git log options]\n"
"Options:\n"
" -l Start up in log view\n"
" -d Start up in diff view\n"
+" -S Start up in status view\n"
" -n[I], --line-number[=I] Show line numbers with given interval\n"
" -b[N], --tab-size[=N] Set number of spaces for tab expansion\n"
" -- Mark end of tig options\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;
for (i = 1; i < argc; i++) {
char *opt = argv[i];
+ if (!strcmp(opt, "log") ||
+ !strcmp(opt, "diff") ||
+ !strcmp(opt, "show")) {
+ opt_request = opt[0] == 'l'
+ ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
+ break;
+ }
+
+ if (opt[0] && opt[0] != '-')
+ break;
+
if (!strcmp(opt, "-l")) {
opt_request = REQ_VIEW_LOG;
continue;
continue;
}
+ if (!strcmp(opt, "-S")) {
+ opt_request = REQ_VIEW_STATUS;
+ break;
+ }
+
if (check_option(opt, 'n', "line-number", OPT_INT, &opt_num_interval)) {
opt_line_number = TRUE;
continue;
break;
}
- if (!strcmp(opt, "log") ||
- !strcmp(opt, "diff") ||
- !strcmp(opt, "show")) {
- opt_request = opt[0] == 'l'
- ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
- break;
- }
-
- if (opt[0] && opt[0] != '-')
- break;
-
die("unknown option '%s'\n\n%s", opt, usage);
}
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)
+LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
+LINE(STAT_SECTION, "", COLOR_DEFAULT, COLOR_BLUE, A_BOLD), \
+LINE(STAT_NONE, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
+LINE(STAT_STAGED, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(STAT_UNSTAGED,"", COLOR_YELLOW, COLOR_DEFAULT, 0), \
+LINE(STAT_UNTRACKED,"", COLOR_MAGENTA, COLOR_DEFAULT, 0)
enum line_type {
#define LINE(type, line, fg, bg, attr) \
{ 'f', REQ_VIEW_BLOB },
{ 'p', REQ_VIEW_PAGER },
{ 'h', REQ_VIEW_HELP },
+ { 'S', REQ_VIEW_STATUS },
/* View manipulation */
{ 'q', REQ_VIEW_CLOSE },
{ 'g', REQ_TOGGLE_REV_GRAPH },
{ ':', REQ_PROMPT },
- /* wgetch() with nodelay() enabled returns ERR when there's no input. */
- { ERR, REQ_NONE },
-
/* Using the ncurses SIGWINCH handler. */
{ KEY_RESIZE, REQ_SCREEN_RESIZE },
};
KEYMAP_(TREE), \
KEYMAP_(BLOB), \
KEYMAP_(PAGER), \
- KEYMAP_(HELP) \
+ KEYMAP_(HELP), \
+ KEYMAP_(STATUS)
enum keymap {
#define KEYMAP_(name) KEYMAP_##name
static char buf[BUFSIZ];
static char key_char[] = "'X'";
size_t pos = 0;
- char *sep = " ";
+ char *sep = "";
int i;
buf[pos] = 0;
static struct view *display[2];
static unsigned int current_view;
+/* Reading from the prompt? */
+static bool input_mode = FALSE;
+
#define foreach_displayed_view(view, i) \
for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
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. */
static struct view_ops main_ops;
static struct view_ops tree_ops;
static struct view_ops blob_ops;
+static struct view_ops help_ops;
+static struct view_ops status_ops;
#define VIEW_STR(name, cmd, env, ref, ops, map) \
{ name, cmd, #env, ref, ops, map}
static struct view views[] = {
- VIEW_(MAIN, "main", &main_ops, ref_head),
- VIEW_(DIFF, "diff", &pager_ops, ref_commit),
- 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_(MAIN, "main", &main_ops, ref_head),
+ VIEW_(DIFF, "diff", &pager_ops, ref_commit),
+ VIEW_(LOG, "log", &pager_ops, ref_head),
+ VIEW_(TREE, "tree", &tree_ops, ref_commit),
+ VIEW_(BLOB, "blob", &blob_ops, ref_blob),
+ VIEW_(HELP, "help", &help_ops, ""),
+ VIEW_(PAGER, "pager", &pager_ops, ""),
+ VIEW_(STATUS, "status", &status_ops, ""),
};
#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
{
struct line *line;
bool selected = (view->offset + lineno == view->lineno);
+ bool draw_ok;
assert(view_is_displayed(view));
wclrtoeol(view->win);
}
- return view->ops->draw(view, line, lineno, selected);
+ scrollok(view->win, FALSE);
+ draw_ok = view->ops->draw(view, line, lineno, selected);
+ scrollok(view->win, TRUE);
+
+ return draw_ok;
}
static void
}
redrawwin(view->win);
- wrefresh(view->win);
+ if (input_mode)
+ wnoutrefresh(view->win);
+ else
+ wrefresh(view->win);
}
static void
static void
update_view_title(struct view *view)
{
- assert(view_is_displayed(view));
-
- if (view == display[current_view])
- wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
- else
- wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
-
- werase(view->title);
- wmove(view->title, 0, 0);
+ char buf[SIZEOF_STR];
+ char state[SIZEOF_STR];
+ size_t bufpos = 0, statelen = 0;
- if (*view->ref)
- wprintw(view->title, "[%s] %s", view->name, view->ref);
- else
- wprintw(view->title, "[%s]", view->name);
+ assert(view_is_displayed(view));
if (view->lines || view->pipe) {
unsigned int view_lines = view->offset + view->height;
? MIN(view_lines, view->lines) * 100 / view->lines
: 0;
- wprintw(view->title, " - %s %d of %d (%d%%)",
- view->ops->type,
- view->lineno + 1,
- view->lines,
- lines);
+ string_format_from(state, &statelen, "- %s %d of %d (%d%%)",
+ view->ops->type,
+ view->lineno + 1,
+ view->lines,
+ lines);
+
+ if (view->pipe) {
+ time_t secs = time(NULL) - view->start_time;
+
+ /* Three git seconds are a long time ... */
+ if (secs > 2)
+ string_format_from(state, &statelen, " %lds", secs);
+ }
}
- if (view->pipe) {
- time_t secs = time(NULL) - view->start_time;
+ string_format_from(buf, &bufpos, "[%s]", view->name);
+ if (*view->ref && bufpos < view->width) {
+ size_t refsize = strlen(view->ref);
+ size_t minsize = bufpos + 1 + /* abbrev= */ 7 + 1 + statelen;
+
+ if (minsize < view->width)
+ refsize = view->width - minsize + 7;
+ string_format_from(buf, &bufpos, " %.*s", refsize, view->ref);
+ }
- /* Three git seconds are a long time ... */
- if (secs > 2)
- wprintw(view->title, " %lds", secs);
+ if (statelen && bufpos < view->width) {
+ string_format_from(buf, &bufpos, " %s", state);
}
+ if (view == display[current_view])
+ wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
+ else
+ wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
+
+ mvwaddnstr(view->title, 0, 0, buf, bufpos);
+ wclrtoeol(view->title);
wmove(view->title, 0, view->width - 1);
- wrefresh(view->title);
+
+ if (input_mode)
+ wnoutrefresh(view->title);
+ else
+ wrefresh(view->title);
}
static void
}
static void
-update_display_cursor(void)
+update_display_cursor(struct view *view)
{
- struct view *view = display[current_view];
-
/* Move the cursor to the right-most column of the cursor line.
*
* XXX: This could turn out to be a bit expensive, but it ensures that
}
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. */
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;
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);
}
report("Allocation failure");
end:
+ view->ops->read(view, NULL);
end_update(view);
return FALSE;
}
-
-/*
- * View opening
- */
-
-static void open_help_view(struct view *view)
+static struct line *
+add_line_data(struct view *view, void *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;
- }
+ memset(line, 0, sizeof(*line));
+ line->type = type;
+ line->data = data;
- view->ops->read(view, "Quick reference for tig keybindings:");
+ return line;
+}
- for (i = 0; i < ARRAY_SIZE(req_info); i++) {
- char *key;
+static struct line *
+add_line_text(struct view *view, char *data, enum line_type type)
+{
+ if (data)
+ data = strdup(data);
- if (!req_info[i].request) {
- view->ops->read(view, "");
- view->ops->read(view, req_info[i].help);
- continue;
- }
+ return data ? add_line_data(view, data, type) : NULL;
+}
- 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. */
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)) {
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:
+ case REQ_VIEW_STATUS:
open_view(view, request, OPEN_DEFAULT);
break;
break;
case REQ_SHOW_VERSION:
- report("%s (built %s)", VERSION, __DATE__);
+ report("tig-%s (built %s)", VERSION, __DATE__);
return TRUE;
case REQ_SCREEN_RESIZE:
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;
- line->data = strdup(data);
- if (!line->data)
- return FALSE;
+ if (!data)
+ return TRUE;
- line->type = get_line_type(line->data);
- view->lines++;
+ line = add_line_text(view, data, get_line_type(data));
+ if (!line)
+ return FALSE;
if (line->type == LINE_COMMIT &&
(view == VIEW(REQ_VIEW_DIFF) ||
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,
static bool
tree_read(struct view *view, char *text)
{
- size_t textlen = strlen(text);
+ size_t textlen = text ? strlen(text) : 0;
char buf[SIZEOF_STR];
unsigned long pos;
enum line_type type;
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 bool
tree_enter(struct view *view, struct line *line)
{
- enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
+ enum open_flags flags;
enum request request;
switch (line->type) {
/* Trees and subtrees share the same ID, so they are not not
* unique like blobs. */
- flags |= OPEN_RELOAD;
+ flags = OPEN_RELOAD;
request = REQ_VIEW_TREE;
break;
case LINE_TREE_FILE:
+ flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
request = REQ_VIEW_BLOB;
break;
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,
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,
/*
- * Main view backend
+ * Status backend
*/
-struct commit {
- char id[SIZEOF_REV]; /* SHA1 ID. */
- char title[75]; /* First line of the commit message. */
- char author[75]; /* Author of the commit. */
- struct tm 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. */
+struct status {
+ char status;
+ struct {
+ mode_t mode;
+ char rev[SIZEOF_REV];
+ } old;
+ struct {
+ mode_t mode;
+ char rev[SIZEOF_REV];
+ } new;
+ char name[SIZEOF_STR];
};
+/* Get fields from the diff line:
+ * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
+ */
+static inline bool
+status_get_diff(struct status *file, char *buf, size_t bufsize)
+{
+ char *old_mode = buf + 1;
+ char *new_mode = buf + 8;
+ char *old_rev = buf + 15;
+ char *new_rev = buf + 56;
+ char *status = buf + 97;
+
+ if (bufsize != 99 ||
+ old_mode[-1] != ':' ||
+ new_mode[-1] != ' ' ||
+ old_rev[-1] != ' ' ||
+ new_rev[-1] != ' ' ||
+ status[-1] != ' ')
+ return FALSE;
+
+ file->status = *status;
+
+ string_copy_rev(file->old.rev, old_rev);
+ string_copy_rev(file->new.rev, new_rev);
+
+ file->old.mode = strtoul(old_mode, NULL, 8);
+ file->new.mode = strtoul(new_mode, NULL, 8);
+
+ file->name[0] = 0;
+
+ return TRUE;
+}
+
static bool
-main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
+status_run(struct view *view, const char cmd[], bool diff, enum line_type type)
{
- char buf[DATE_COLS + 1];
- struct commit *commit = line->data;
- enum line_type type;
- int col = 0;
- size_t timelen;
- size_t authorlen;
- int trimmed = 1;
+ struct status *file = NULL;
+ char buf[SIZEOF_STR * 4];
+ size_t bufsize = 0;
+ FILE *pipe;
- if (!*commit->author)
+ pipe = popen(cmd, "r");
+ if (!pipe)
return FALSE;
- wmove(view->win, lineno, col);
+ add_line_data(view, NULL, type);
- if (selected) {
- type = LINE_CURSOR;
- wattrset(view->win, get_line_attr(type));
- wchgat(view->win, -1, 0, type, NULL);
+ while (!feof(pipe) && !ferror(pipe)) {
+ char *sep;
+ size_t readsize;
- } else {
- type = LINE_MAIN_COMMIT;
- wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
- }
+ readsize = fread(buf + bufsize, 1, sizeof(buf) - bufsize, pipe);
+ if (!readsize)
+ break;
+ bufsize += readsize;
- timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
- waddnstr(view->win, buf, timelen);
- waddstr(view->win, " ");
+ /* Process while we have NUL chars. */
+ while ((sep = memchr(buf, 0, bufsize))) {
+ size_t sepsize = sep - buf + 1;
- col += DATE_COLS;
- wmove(view->win, lineno, col);
- if (type != LINE_CURSOR)
- wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
+ if (!file) {
+ if (!realloc_lines(view, view->line_size + 1))
+ goto error_out;
- if (opt_utf8) {
- authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
- } else {
- authorlen = strlen(commit->author);
- if (authorlen > AUTHOR_COLS - 2) {
- authorlen = AUTHOR_COLS - 2;
- trimmed = 1;
+ file = calloc(1, sizeof(*file));
+ if (!file)
+ goto error_out;
+
+ add_line_data(view, file, type);
+ }
+
+ /* Parse diff info part. */
+ if (!diff) {
+ file->status = '?';
+
+ } else if (!file->status) {
+ if (!status_get_diff(file, buf, sepsize))
+ goto error_out;
+
+ bufsize -= sepsize;
+ memmove(buf, sep + 1, bufsize);
+
+ sep = memchr(buf, 0, bufsize);
+ if (!sep)
+ break;
+ sepsize = sep - buf + 1;
+ }
+
+ /* git-ls-files just delivers a NUL separated
+ * list of file names similar to the second half
+ * of the git-diff-* output. */
+ string_ncopy(file->name, buf, sepsize);
+ bufsize -= sepsize;
+ memmove(buf, sep + 1, bufsize);
+ file = NULL;
}
}
- if (trimmed) {
- waddnstr(view->win, commit->author, authorlen);
- if (type != LINE_CURSOR)
- wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
- waddch(view->win, '~');
- } else {
- waddstr(view->win, commit->author);
+ if (ferror(pipe)) {
+error_out:
+ pclose(pipe);
+ return FALSE;
}
- col += AUTHOR_COLS;
- if (type != LINE_CURSOR)
- wattrset(view->win, A_NORMAL);
+ if (!view->line[view->lines - 1].data)
+ add_line_data(view, NULL, LINE_STAT_NONE);
- if (opt_rev_graph && commit->graph_size) {
- size_t i;
+ pclose(pipe);
+ return TRUE;
+}
- wmove(view->win, lineno, col);
- /* Using waddch() instead of waddnstr() ensures that
- * they'll be rendered correctly for the cursor line. */
- for (i = 0; i < commit->graph_size; i++)
- waddch(view->win, commit->graph[i]);
+#define STATUS_DIFF_INDEX_CMD "git diff-index -z --cached HEAD"
+#define STATUS_DIFF_FILES_CMD "git diff-files -z"
+#define STATUS_LIST_OTHER_CMD \
+ "_git_exclude=$(git rev-parse --git-dir)/info/exclude;" \
+ "test -f \"$_git_exclude\" && exclude=\"--exclude-from=$_git_exclude\";" \
+ "git ls-files -z --others --exclude-per-directory=.gitignore \"$exclude\"" \
- col += commit->graph_size + 1;
+/* First parse staged info using git-diff-index(1), then parse unstaged
+ * info using git-diff-files(1), and finally untracked files using
+ * git-ls-files(1). */
+static bool
+status_open(struct view *view)
+{
+ size_t i;
+
+ for (i = 0; i < view->lines; i++)
+ free(view->line[i].data);
+ free(view->line);
+ view->lines = view->line_size = 0;
+ view->line = NULL;
+
+ if (!realloc_lines(view, view->line_size + 6))
+ return FALSE;
+
+ 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, STATUS_LIST_OTHER_CMD, FALSE, LINE_STAT_UNTRACKED))
+ return FALSE;
+
+ return TRUE;
+}
+
+static bool
+status_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
+{
+ struct status *status = line->data;
+
+ wmove(view->win, lineno, 0);
+
+ if (selected) {
+ wattrset(view->win, get_line_attr(LINE_CURSOR));
+ wchgat(view->win, -1, 0, LINE_CURSOR, NULL);
+
+ } else if (!status && line->type != LINE_STAT_NONE) {
+ wattrset(view->win, get_line_attr(LINE_STAT_SECTION));
+ wchgat(view->win, -1, 0, LINE_STAT_SECTION, NULL);
+
+ } else {
+ wattrset(view->win, get_line_attr(line->type));
}
- wmove(view->win, lineno, col);
+ if (!status) {
+ char *text;
- if (commit->refs) {
- size_t i = 0;
+ switch (line->type) {
+ case LINE_STAT_STAGED:
+ text = "Changes to be committed:";
+ break;
- do {
- if (type == LINE_CURSOR)
- ;
- else if (commit->refs[i]->tag)
- wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
- else
- wattrset(view->win, get_line_attr(LINE_MAIN_REF));
- waddstr(view->win, "[");
- waddstr(view->win, commit->refs[i]->name);
- waddstr(view->win, "]");
- if (type != LINE_CURSOR)
- wattrset(view->win, A_NORMAL);
- waddstr(view->win, " ");
- col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
- } while (commit->refs[i++]->next);
+ case LINE_STAT_UNSTAGED:
+ text = "Changed but not updated:";
+ break;
+
+ case LINE_STAT_UNTRACKED:
+ text = "Untracked files:";
+ break;
+
+ case LINE_STAT_NONE:
+ text = " (no files)";
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ waddstr(view->win, text);
+ return TRUE;
}
- if (type != LINE_CURSOR)
- wattrset(view->win, get_line_attr(type));
+ waddch(view->win, status->status);
+ if (!selected)
+ wattrset(view->win, A_NORMAL);
+ wmove(view->win, lineno, 4);
+ waddstr(view->win, status->name);
- {
- int titlelen = strlen(commit->title);
+ return TRUE;
+}
- if (col + titlelen > view->width)
- titlelen = view->width - col;
+static bool
+status_enter(struct view *view, struct line *line)
+{
+ struct status *status = line->data;
+ const char *cmd;
+ char buf[SIZEOF_STR];
+ size_t bufsize = 0;
+ size_t written = 0;
+ FILE *pipe;
- waddnstr(view->win, commit->title, titlelen);
+ if (!status)
+ return TRUE;
+
+ switch (line->type) {
+ case LINE_STAT_STAGED:
+ if (!string_format_from(buf, &bufsize, "%06o %s\t%s%c",
+ status->old.mode,
+ status->old.rev,
+ status->name, 0))
+ return FALSE;
+ cmd = "git update-index -z --index-info";
+ break;
+
+ case LINE_STAT_UNSTAGED:
+ case LINE_STAT_UNTRACKED:
+ if (!string_format_from(buf, &bufsize, "%s%c", status->name, 0))
+ return FALSE;
+ cmd = "git update-index -z --add --remove --stdin";
+ break;
+
+ default:
+ die("w00t");
+ }
+
+ pipe = popen(cmd, "w");
+ if (!pipe)
+ return FALSE;
+
+ while (!ferror(pipe) && written < bufsize) {
+ written += fwrite(buf + written, 1, bufsize - written, pipe);
}
+ pclose(pipe);
+
+ if (written != bufsize)
+ return FALSE;
+
+ open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
return TRUE;
}
+static void
+status_select(struct view *view, struct line *line)
+{
+ char *text;
+
+ switch (line->type) {
+ case LINE_STAT_STAGED:
+ text = "Press Enter to unstage file for commit";
+ break;
+
+ case LINE_STAT_UNSTAGED:
+ text = "Press Enter to stage file for commit ";
+ break;
+
+ case LINE_STAT_UNTRACKED:
+ text = "Press Enter to stage file for addition";
+ break;
+
+ case LINE_STAT_NONE:
+ return;
+
+ default:
+ die("w00t");
+ }
+
+ string_ncopy(view->ref, text, strlen(text));
+}
+
+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++) {
+ char *text;
+
+ switch (state) {
+ case S_NAME: text = status->name; break;
+ case S_STATUS:
+ buf[0] = status->status;
+ text = buf;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (regexec(view->regex, text, 1, &pmatch, 0) != REG_NOMATCH)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static struct view_ops status_ops = {
+ "file",
+ status_open,
+ NULL,
+ status_draw,
+ status_enter,
+ status_grep,
+ status_select,
+};
+
+
+/*
+ * Revision graph
+ */
+
+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. */
+ struct ref **refs; /* Repository references. */
+ chtype graph[SIZEOF_REVGRAPH]; /* Ancestry chain graphics. */
+ size_t graph_size; /* The width of the graph array. */
+};
-struct rev_stack {
- struct rev_stack *prev, *next, *parents;
+/* Size of rev graph with no "padding" columns */
+#define SIZEOF_REVITEMS (SIZEOF_REVGRAPH - (SIZEOF_REVGRAPH / 2))
+
+struct rev_graph {
+ struct rev_graph *prev, *next, *parents;
char rev[SIZEOF_REVITEMS][SIZEOF_REV];
size_t size;
struct commit *commit;
};
/* Parents of the commit being visualized. */
-static struct rev_stack graph_parents[3];
+static struct rev_graph graph_parents[4];
/* The current stack of revisions on the graph. */
-static struct rev_stack graph_stacks[3] = {
- { &graph_stacks[2], &graph_stacks[1], &graph_parents[0] },
+static struct rev_graph graph_stacks[4] = {
+ { &graph_stacks[3], &graph_stacks[1], &graph_parents[0] },
{ &graph_stacks[0], &graph_stacks[2], &graph_parents[1] },
- { &graph_stacks[1], &graph_stacks[0], &graph_parents[2] },
+ { &graph_stacks[1], &graph_stacks[3], &graph_parents[2] },
+ { &graph_stacks[2], &graph_stacks[0], &graph_parents[3] },
};
static inline bool
-graph_parent_is_merge(struct rev_stack *graph)
+graph_parent_is_merge(struct rev_graph *graph)
{
return graph->parents->size > 1;
}
static inline void
-append_to_rev_graph(struct rev_stack *stack, chtype symbol)
+append_to_rev_graph(struct rev_graph *graph, chtype symbol)
{
- if (stack->commit->graph_size < ARRAY_SIZE(stack->commit->graph) - 1)
- stack->commit->graph[stack->commit->graph_size++] = symbol;
+ struct commit *commit = graph->commit;
+
+ if (commit->graph_size < ARRAY_SIZE(commit->graph) - 1)
+ commit->graph[commit->graph_size++] = symbol;
}
static void
-done_rev_graph(struct rev_stack *graph)
+done_rev_graph(struct rev_graph *graph)
{
if (graph_parent_is_merge(graph) &&
graph->pos < graph->size - 1 &&
}
static void
-push_rev_stack(struct rev_stack *stack, char *parent)
+push_rev_graph(struct rev_graph *graph, char *parent)
{
- /* Combine duplicate parents lines. */
- if (stack->size > 0 &&
- !strncmp(stack->rev[stack->size - 1], parent, SIZEOF_REV))
- return;
+ int i;
- if (stack->size < SIZEOF_REVITEMS) {
- string_ncopy(stack->rev[stack->size++], parent, SIZEOF_REV);
+ /* "Collapse" duplicate parents lines.
+ *
+ * FIXME: This needs to also update update the drawn graph but
+ * for now it just serves as a method for pruning graph lines. */
+ for (i = 0; i < graph->size; i++)
+ if (!strncmp(graph->rev[i], parent, SIZEOF_REV))
+ return;
+
+ if (graph->size < SIZEOF_REVITEMS) {
+ string_ncopy(graph->rev[graph->size++], parent, SIZEOF_REV);
}
}
-static void
-draw_rev_graph(struct rev_stack *graph)
+static chtype
+get_rev_graph_symbol(struct rev_graph *graph)
{
- chtype symbol, separator, line;
- size_t i;
+ chtype symbol;
- /* Place the symbol for this commit. */
if (graph->parents->size == 0)
symbol = REVGRAPH_INIT;
- else if (graph->parents->size > 1)
+ else if (graph_parent_is_merge(graph))
symbol = REVGRAPH_MERGE;
else if (graph->pos >= graph->size)
symbol = REVGRAPH_BRANCH;
else
symbol = REVGRAPH_COMMIT;
- separator = ' ';
- line = REVGRAPH_LINE;
+ return symbol;
+}
+
+static void
+draw_rev_graph(struct rev_graph *graph)
+{
+ struct rev_filler {
+ chtype separator, line;
+ };
+ enum { DEFAULT, RSHARP, RDIAG, LDIAG };
+ static struct rev_filler fillers[] = {
+ { ' ', REVGRAPH_LINE },
+ { '`', '.' },
+ { '\'', ' ' },
+ { '/', ' ' },
+ };
+ chtype symbol = get_rev_graph_symbol(graph);
+ struct rev_filler *filler;
+ size_t i;
+
+ filler = &fillers[DEFAULT];
for (i = 0; i < graph->pos; i++) {
- append_to_rev_graph(graph, line);
+ append_to_rev_graph(graph, filler->line);
if (graph_parent_is_merge(graph->prev) &&
- graph->prev->pos == i) {
- separator = '`';
- line = '.';
- }
- append_to_rev_graph(graph, separator);
+ graph->prev->pos == i)
+ filler = &fillers[RSHARP];
+
+ append_to_rev_graph(graph, filler->separator);
}
+ /* Place the symbol for this revision. */
append_to_rev_graph(graph, symbol);
- if (graph->prev->size > graph->size) {
- separator = '\'';
- line = ' ';
- } else {
- separator = ' ';
- line = REVGRAPH_LINE;
- }
+ if (graph->prev->size > graph->size)
+ filler = &fillers[RDIAG];
+ else
+ filler = &fillers[DEFAULT];
+
i++;
for (; i < graph->size; i++) {
- append_to_rev_graph(graph, separator);
- append_to_rev_graph(graph, line);
- if (graph_parent_is_merge(graph->prev)) {
- if (i < graph->prev->pos + graph->parents->size) {
- separator = '`';
- line = '.';
- }
- }
- if (graph->prev->size > graph->size) {
- separator = '/';
- line = ' ';
- }
+ append_to_rev_graph(graph, filler->separator);
+ append_to_rev_graph(graph, filler->line);
+ if (graph_parent_is_merge(graph->prev) &&
+ i < graph->prev->pos + graph->parents->size)
+ filler = &fillers[RSHARP];
+ if (graph->prev->size > graph->size)
+ filler = &fillers[LDIAG];
}
if (graph->prev->size > graph->size) {
- append_to_rev_graph(graph, separator);
- if (line != ' ')
- append_to_rev_graph(graph, line);
+ append_to_rev_graph(graph, filler->separator);
+ if (filler->line != ' ')
+ append_to_rev_graph(graph, filler->line);
}
}
-void
-update_rev_graph(struct rev_stack *graph)
+/* Prepare the next rev graph */
+static void
+prepare_rev_graph(struct rev_graph *graph)
{
size_t i;
- /* First traverse all lines of revisions up to the active one. */
+ /* First, traverse all lines of revisions up to the active one. */
for (graph->pos = 0; graph->pos < graph->size; graph->pos++) {
if (!strcmp(graph->rev[graph->pos], graph->commit->id))
break;
- push_rev_stack(graph->next, graph->rev[graph->pos]);
+ push_rev_graph(graph->next, graph->rev[graph->pos]);
}
+ /* Interleave the new revision parent(s). */
for (i = 0; i < graph->parents->size; i++)
- push_rev_stack(graph->next, graph->parents->rev[i]);
+ push_rev_graph(graph->next, graph->parents->rev[i]);
- /* FIXME: Moving branches left and right when collapsing a branch. */
+ /* Lastly, put any remaining revisions. */
for (i = graph->pos + 1; i < graph->size; i++)
- push_rev_stack(graph->next, graph->rev[i]);
+ push_rev_graph(graph->next, graph->rev[i]);
+}
+
+static void
+update_rev_graph(struct rev_graph *graph)
+{
+ /* If this is the finalizing update ... */
+ if (graph->commit)
+ prepare_rev_graph(graph);
+
+ /* Graph visualization needs a one rev look-ahead,
+ * so the first update doesn't visualize anything. */
+ if (!graph->prev->commit)
+ return;
+
+ draw_rev_graph(graph->prev);
+ done_rev_graph(graph->prev->prev);
+}
+
+
+/*
+ * Main view backend
+ */
+
+static bool
+main_draw(struct view *view, struct line *line, unsigned int lineno, bool selected)
+{
+ char buf[DATE_COLS + 1];
+ struct commit *commit = line->data;
+ enum line_type type;
+ int col = 0;
+ size_t timelen;
+ size_t authorlen;
+ int trimmed = 1;
+
+ if (!*commit->author)
+ return FALSE;
+
+ wmove(view->win, lineno, col);
+
+ if (selected) {
+ type = LINE_CURSOR;
+ wattrset(view->win, get_line_attr(type));
+ wchgat(view->win, -1, 0, type, NULL);
+
+ } else {
+ type = LINE_MAIN_COMMIT;
+ wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
+ }
+
+ timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
+ waddnstr(view->win, buf, timelen);
+ waddstr(view->win, " ");
+
+ col += DATE_COLS;
+ wmove(view->win, lineno, col);
+ if (type != LINE_CURSOR)
+ wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
+
+ if (opt_utf8) {
+ authorlen = utf8_length(commit->author, AUTHOR_COLS - 2, &col, &trimmed);
+ } else {
+ authorlen = strlen(commit->author);
+ if (authorlen > AUTHOR_COLS - 2) {
+ authorlen = AUTHOR_COLS - 2;
+ trimmed = 1;
+ }
+ }
+
+ if (trimmed) {
+ waddnstr(view->win, commit->author, authorlen);
+ if (type != LINE_CURSOR)
+ wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
+ waddch(view->win, '~');
+ } else {
+ waddstr(view->win, commit->author);
+ }
+
+ col += AUTHOR_COLS;
+ if (type != LINE_CURSOR)
+ wattrset(view->win, A_NORMAL);
+
+ if (opt_rev_graph && commit->graph_size) {
+ size_t i;
+
+ wmove(view->win, lineno, col);
+ /* Using waddch() instead of waddnstr() ensures that
+ * they'll be rendered correctly for the cursor line. */
+ for (i = 0; i < commit->graph_size; i++)
+ waddch(view->win, commit->graph[i]);
+
+ waddch(view->win, ' ');
+ col += commit->graph_size + 1;
+ }
- draw_rev_graph(graph);
- done_rev_graph(graph->prev);
+ wmove(view->win, lineno, col);
+
+ if (commit->refs) {
+ size_t i = 0;
+
+ do {
+ if (type == LINE_CURSOR)
+ ;
+ 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, "[");
+ waddstr(view->win, commit->refs[i]->name);
+ waddstr(view->win, "]");
+ if (type != LINE_CURSOR)
+ wattrset(view->win, A_NORMAL);
+ waddstr(view->win, " ");
+ col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
+ } while (commit->refs[i++]->next);
+ }
+
+ if (type != LINE_CURSOR)
+ wattrset(view->win, get_line_attr(type));
+
+ {
+ int titlelen = strlen(commit->title);
+
+ if (col + titlelen > view->width)
+ titlelen = view->width - col;
+
+ waddnstr(view->win, commit->title, titlelen);
+ }
+
+ return TRUE;
}
/* Reads git log --pretty=raw output and parses it into the commit struct. */
static bool
main_read(struct view *view, char *line)
{
- static struct rev_stack *graph = graph_stacks;
- enum line_type type = get_line_type(line);
- struct commit *commit = view->lines
- ? view->line[view->lines - 1].data : NULL;
+ static struct rev_graph *graph = graph_stacks;
+ enum line_type type;
+ struct commit *commit;
- switch (type) {
- case LINE_COMMIT:
+ if (!line) {
+ update_rev_graph(graph);
+ return TRUE;
+ }
+
+ type = get_line_type(line);
+ if (type == LINE_COMMIT) {
commit = calloc(1, sizeof(struct commit));
if (!commit)
return FALSE;
- line += STRING_SIZE("commit ");
-
- view->line[view->lines++].data = commit;
- string_copy(commit->id, line);
+ string_copy_rev(commit->id, line + STRING_SIZE("commit "));
commit->refs = get_refs(commit->id);
graph->commit = commit;
- break;
+ add_line_data(view, commit, LINE_MAIN_COMMIT);
+ return TRUE;
+ }
+ if (!view->lines)
+ return TRUE;
+ commit = view->line[view->lines - 1].data;
+
+ switch (type) {
case LINE_PARENT:
- if (commit) {
- line += STRING_SIZE("parent ");
- push_rev_stack(graph->parents, line);
- }
+ push_rev_graph(graph->parents, line + STRING_SIZE("parent "));
break;
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 (!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 (emailend[1] == ' ') {
+ char *secs = emailend + 2;
+ char *zone = strchr(secs, ' ');
+ time_t time = (time_t) atol(secs);
- if (!secs || secs[1] != ' ')
- break;
-
- 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;
}
default:
- if (!commit)
- break;
-
/* Fill in the commit title if it has not already been set. */
if (commit->title[0])
break;
/* Require titles to start with a non-space character at the
* offset used by git log. */
- /* FIXME: More gracefull handling of titles; append "..." to
- * shortened titles, etc. */
- if (strncmp(line, " ", 4) ||
- isspace(line[4]))
+ if (strncmp(line, " ", 4))
+ break;
+ line += 4;
+ /* Well, if the title starts with a whitespace character,
+ * try to be forgiving. Otherwise we end up with no title. */
+ while (isspace(*line))
+ line++;
+ if (*line == '\0')
break;
+ /* FIXME: More graceful handling of titles; append "..." to
+ * shortened titles, etc. */
- string_copy(commit->title, line + 4);
+ string_copy(commit->title, line);
}
return TRUE;
{
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,
/* The status window is used for polling keystrokes. */
static WINDOW *status_win;
+static bool status_empty = TRUE;
+
/* Update status and title window. */
static void
report(const char *msg, ...)
{
- static bool empty = TRUE;
struct view *view = display[current_view];
- if (!empty || *msg) {
+ if (input_mode)
+ return;
+
+ if (!status_empty || *msg) {
va_list args;
va_start(args, msg);
- werase(status_win);
wmove(status_win, 0, 0);
if (*msg) {
vwprintw(status_win, msg, args);
- empty = FALSE;
+ status_empty = FALSE;
} else {
- empty = TRUE;
+ status_empty = TRUE;
}
+ wclrtoeol(status_win);
wrefresh(status_win);
va_end(args);
}
update_view_title(view);
- update_display_cursor();
+ update_display_cursor(view);
}
/* Controls when nodelay should be in effect when polling user input. */
struct view *view;
int i, key;
+ input_mode = TRUE;
+
foreach_view (view, i)
update_view(view);
- report("%s%.*s", prompt, pos, buf);
+ input_mode = FALSE;
+
+ mvwprintw(status_win, 0, 0, "%s%.*s", prompt, pos, buf);
+ wclrtoeol(status_win);
+
/* Refresh, accept single keystroke of input */
key = wgetch(status_win);
switch (key) {
}
}
- if (status == CANCEL) {
- /* Clear the status window */
- report("");
+ /* Clear the status window */
+ status_empty = FALSE;
+ report("");
+
+ if (status == CANCEL)
return NULL;
- }
buf[pos++] = 0;
{
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;
- string_copy(ref->id, id);
+ ref->remote = remote;
+ string_copy_rev(ref->id, id);
return OK;
}
/* Refresh, accept single keystroke of input */
key = wgetch(status_win);
+ /* wgetch() with nodelay() enabled returns ERR when there's no
+ * input. */
+ if (key == ERR) {
+ request = REQ_NONE;
+ continue;
+ }
+
request = get_keybinding(display[current_view]->keymap, key);
/* Some low-level request handling. This keeps access to