index 9e80d15b7fb61bd886a648f1387a0dd1828cb1ff..f72932144b4d893db73720d3946da34d15b97ee7 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -666,6 +666,7 @@ static int read_properties(struct io *io, const char *separators, int (*read)(ch
REQ_(ENTER, "Enter current line and scroll"), \
REQ_(NEXT, "Move to next"), \
REQ_(PREVIOUS, "Move to previous"), \
+ REQ_(PARENT, "Move to parent"), \
REQ_(VIEW_NEXT, "Move focus to next view"), \
REQ_(REFRESH, "Reload and refresh"), \
REQ_(MAXIMIZE, "Maximize the current view"), \
@@ -677,7 +678,6 @@ static int read_properties(struct io *io, const char *separators, int (*read)(ch
REQ_(STATUS_REVERT, "Revert file changes"), \
REQ_(STATUS_MERGE, "Merge file using external tool"), \
REQ_(STAGE_NEXT, "Find next chunk to stage"), \
- REQ_(TREE_PARENT, "Switch to parent directory in tree view"), \
\
REQ_GROUP("Cursor navigation") \
REQ_(MOVE_UP, "Move cursor one line up"), \
static iconv_t opt_iconv = ICONV_NONE;
static char opt_search[SIZEOF_STR] = "";
static char opt_cdup[SIZEOF_STR] = "";
+static char opt_prefix[SIZEOF_STR] = "";
static char opt_git_dir[SIZEOF_STR] = "";
static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
static char opt_editor[SIZEOF_STR] = "";
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_PARENT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_BOLD), \
+LINE(TREE_MODE, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
+LINE(TREE_DIR, "", COLOR_YELLOW, COLOR_DEFAULT, A_NORMAL), \
LINE(TREE_FILE, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
LINE(STAT_HEAD, "", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(STAT_SECTION, "", COLOR_CYAN, COLOR_DEFAULT, 0), \
{ '!', REQ_STATUS_REVERT },
{ 'M', REQ_STATUS_MERGE },
{ '@', REQ_STAGE_NEXT },
- { ',', REQ_TREE_PARENT },
+ { ',', REQ_PARENT },
{ 'e', REQ_EDIT },
};
request = get_request(argv[2]);
if (request == REQ_NONE) {
- const char *obsolete[] = { "cherry-pick", "screen-resize" };
+ struct {
+ const char *name;
+ enum request request;
+ } obsolete[] = {
+ { "cherry-pick", REQ_NONE },
+ { "screen-resize", REQ_NONE },
+ { "tree-parent", REQ_PARENT },
+ };
size_t namelen = strlen(argv[2]);
int i;
for (i = 0; i < ARRAY_SIZE(obsolete); i++) {
- if (namelen == strlen(obsolete[i]) &&
- !string_enum_compare(obsolete[i], argv[2], namelen)) {
- config_msg = "Obsolete request name";
- return ERR;
- }
+ if (namelen != strlen(obsolete[i].name) ||
+ string_enum_compare(obsolete[i].name, argv[2], namelen))
+ continue;
+ if (obsolete[i].request != REQ_NONE)
+ add_keybinding(keymap, obsolete[i].request, key);
+ config_msg = "Obsolete request name";
+ return ERR;
}
}
if (request == REQ_NONE && *argv[2]++ == '!')
line = out_buffer;
}
- if (!view->ops->read(view, line))
- goto alloc_error;
+ if (!view->ops->read(view, line)) {
+ report("Allocation failure");
+ end_update(view, TRUE);
+ return FALSE;
+ }
}
{
* commit reference in view->ref it'll be available here. */
update_view_title(view);
return TRUE;
-
-alloc_error:
- report("Allocation failure");
- end_update(view, TRUE);
- return FALSE;
}
static struct line *
resize_display();
if (view->ops->open) {
+ if (view->pipe)
+ end_update(view, TRUE);
if (!view->ops->open(view)) {
report("Failed to load %s view", view->name);
return;
}
+/*
+ * View backend utilities
+ */
+
+/* Parse author lines where the name may be empty:
+ * author <email@address.tld> 1138474660 +0100
+ */
+static void
+parse_author_line(char *ident, char *author, size_t authorsize, struct tm *tm)
+{
+ char *nameend = strchr(ident, '<');
+ char *emailend = strchr(ident, '>');
+
+ if (nameend && emailend)
+ *nameend = *emailend = 0;
+ ident = chomp_string(ident);
+ if (!*ident) {
+ if (nameend)
+ ident = chomp_string(nameend + 1);
+ if (!*ident)
+ ident = "Unknown";
+ }
+
+ string_ncopy_do(author, authorsize, ident, strlen(ident));
+
+ /* Parse epoch and timezone */
+ if (emailend && emailend[1] == ' ') {
+ char *secs = emailend + 2;
+ char *zone = strchr(secs, ' ');
+ time_t time = (time_t) atol(secs);
+
+ if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
+ long tz;
+
+ zone++;
+ tz = ('0' - zone[1]) * 60 * 60 * 10;
+ tz += ('0' - zone[2]) * 60 * 60;
+ tz += ('0' - zone[3]) * 60;
+ tz += ('0' - zone[4]) * 60;
+
+ if (zone[0] == '-')
+ tz = -tz;
+
+ time -= tz;
+ }
+
+ gmtime_r(&time, tm);
+ }
+}
+
/*
* Pager backend
*/
#define SIZEOF_TREE_ATTR \
STRING_SIZE("100644 blob ed09fe897f3c7c9af90bcf80cae92558ea88ae38\t")
-#define TREE_UP_FORMAT "040000 tree %s\t.."
+#define SIZEOF_TREE_MODE \
+ STRING_SIZE("100644 ")
+
+#define TREE_ID_OFFSET \
+ STRING_SIZE("100644 blob ")
+
+struct tree_entry {
+ char id[SIZEOF_REV];
+ mode_t mode;
+ struct tm time; /* Date from the author ident. */
+ char author[75]; /* Author of the commit. */
+ char name[1];
+};
static const char *
tree_path(struct line *line)
{
- const char *path = line->data;
-
- return path + SIZEOF_TREE_ATTR;
+ return ((struct tree_entry *) line->data)->name;
}
+
static int
tree_compare_entry(struct line *line1, struct line *line2)
{
return strcmp(tree_path(line1), tree_path(line2));
}
+static struct line *
+tree_entry(struct view *view, enum line_type type, const char *path,
+ const char *mode, const char *id)
+{
+ struct tree_entry *entry = calloc(1, sizeof(*entry) + strlen(path));
+ struct line *line = entry ? add_line_data(view, entry, type) : NULL;
+
+ if (!entry || !line) {
+ free(entry);
+ return NULL;
+ }
+
+ strncpy(entry->name, path, strlen(path));
+ if (mode)
+ entry->mode = strtoul(mode, NULL, 8);
+ if (id)
+ string_copy_rev(entry->id, id);
+
+ return line;
+}
+
+static bool
+tree_read_date(struct view *view, char *text, bool *read_date)
+{
+ static char author_name[SIZEOF_STR];
+ static struct tm author_time;
+
+ if (!text && *read_date) {
+ *read_date = FALSE;
+ return TRUE;
+
+ } else if (!text) {
+ char *path = *opt_path ? opt_path : ".";
+ /* Find next entry to process */
+ const char *log_file[] = {
+ "git", "log", "--no-color", "--pretty=raw",
+ "--cc", "--raw", view->id, "--", path, NULL
+ };
+ struct io io = {};
+
+ if (!run_io_rd(&io, log_file, FORMAT_NONE)) {
+ report("Failed to load tree data");
+ return TRUE;
+ }
+
+ done_io(view->pipe);
+ view->io = io;
+ *read_date = TRUE;
+ return FALSE;
+
+ } else if (*text == 'a' && get_line_type(text) == LINE_AUTHOR) {
+ parse_author_line(text + STRING_SIZE("author "),
+ author_name, sizeof(author_name), &author_time);
+
+ } else if (*text == ':') {
+ char *pos;
+ size_t annotated = 1;
+ size_t i;
+
+ pos = strchr(text, '\t');
+ if (!pos)
+ return TRUE;
+ text = pos + 1;
+ if (*opt_prefix && !strncmp(text, opt_prefix, strlen(opt_prefix)))
+ text += strlen(opt_prefix);
+ if (*opt_path && !strncmp(text, opt_path, strlen(opt_path)))
+ text += strlen(opt_path);
+ pos = strchr(text, '/');
+ if (pos)
+ *pos = 0;
+
+ for (i = 1; i < view->lines; i++) {
+ struct line *line = &view->line[i];
+ struct tree_entry *entry = line->data;
+
+ annotated += !!*entry->author;
+ if (*entry->author || strcmp(entry->name, text))
+ continue;
+
+ string_copy(entry->author, author_name);
+ memcpy(&entry->time, &author_time, sizeof(entry->time));
+ line->dirty = 1;
+ break;
+ }
+
+ if (annotated == view->lines)
+ kill_io(view->pipe);
+ }
+ return TRUE;
+}
+
static bool
tree_read(struct view *view, char *text)
{
- size_t textlen = text ? strlen(text) : 0;
+ static bool read_date = FALSE;
+ struct tree_entry *data;
struct line *entry, *line;
enum line_type type;
+ size_t textlen = text ? strlen(text) : 0;
+ char *path = text + SIZEOF_TREE_ATTR;
+
+ if (read_date || !text)
+ return tree_read_date(view, text, &read_date);
- if (!text)
- return TRUE;
if (textlen <= SIZEOF_TREE_ATTR)
return FALSE;
-
- type = text[STRING_SIZE("100644 ")] == 't'
- ? LINE_TREE_DIR : LINE_TREE_FILE;
-
if (view->lines == 0 &&
- !add_line_format(view, LINE_DEFAULT, "Directory path /%s", opt_path))
+ !tree_entry(view, LINE_TREE_PARENT, opt_path, NULL, NULL))
return FALSE;
/* Strip the path part ... */
if (*opt_path) {
size_t pathlen = textlen - SIZEOF_TREE_ATTR;
size_t striplen = strlen(opt_path);
- char *path = text + SIZEOF_TREE_ATTR;
if (pathlen > striplen)
memmove(path, path + striplen,
/* Insert "link" to parent directory. */
if (view->lines == 1 &&
- !add_line_format(view, LINE_TREE_DIR, TREE_UP_FORMAT, view->ref))
+ !tree_entry(view, LINE_TREE_DIR, "..", "040000", view->ref))
return FALSE;
}
- entry = add_line_text(view, text, type);
+ type = text[SIZEOF_TREE_MODE] == 't' ? LINE_TREE_DIR : LINE_TREE_FILE;
+ entry = tree_entry(view, type, path, text, text + TREE_ID_OFFSET);
if (!entry)
return FALSE;
- text = entry->data;
+ data = entry->data;
/* Skip "Directory ..." and ".." line. */
for (line = &view->line[1 + !!*opt_path]; line < entry; line++) {
memmove(line + 1, line, (entry - line) * sizeof(*entry));
- line->data = text;
+ line->data = data;
line->type = type;
for (; line <= entry; line++)
line->dirty = line->cleareol = 1;
return TRUE;
}
+static bool
+tree_draw(struct view *view, struct line *line, unsigned int lineno)
+{
+ struct tree_entry *entry = line->data;
+
+ if (line->type == LINE_TREE_PARENT) {
+ if (draw_text(view, line->type, "Directory path /", TRUE))
+ return TRUE;
+ } else {
+ char mode[11] = "-r--r--r--";
+
+ if (S_ISDIR(entry->mode)) {
+ mode[3] = mode[6] = mode[9] = 'x';
+ mode[0] = 'd';
+ }
+ if (S_ISLNK(entry->mode))
+ mode[0] = 'l';
+ if (entry->mode & S_IWUSR)
+ mode[2] = 'w';
+ if (entry->mode & S_IXUSR)
+ mode[3] = 'x';
+ if (entry->mode & S_IXGRP)
+ mode[6] = 'x';
+ if (entry->mode & S_IXOTH)
+ mode[9] = 'x';
+ if (draw_field(view, LINE_TREE_MODE, mode, 11, TRUE))
+ return TRUE;
+
+ if (opt_author &&
+ draw_field(view, LINE_MAIN_AUTHOR, entry->author, opt_author_cols, TRUE))
+ return TRUE;
+
+ if (opt_date && draw_date(view, *entry->author ? &entry->time : NULL))
+ return TRUE;
+ }
+ if (draw_text(view, line->type, entry->name, TRUE))
+ return TRUE;
+ return TRUE;
+}
+
static void
open_blob_editor()
{
}
return REQ_NONE;
- case REQ_TREE_PARENT:
+ case REQ_PARENT:
if (!*opt_path) {
/* quit view if at top of tree */
return REQ_VIEW_CLOSE;
break;
default:
- return TRUE;
+ return REQ_NONE;
}
open_view(view, request, flags);
- if (request == REQ_VIEW_TREE) {
+ if (request == REQ_VIEW_TREE)
view->lineno = tree_lineno;
- }
return REQ_NONE;
}
static void
tree_select(struct view *view, struct line *line)
{
- char *text = (char *)line->data + STRING_SIZE("100644 blob ");
+ struct tree_entry *entry = line->data;
if (line->type == LINE_TREE_FILE) {
- string_copy_rev(ref_blob, text);
+ string_copy_rev(ref_blob, entry->id);
string_format(opt_file, "%s%s", opt_path, tree_path(line));
} else if (line->type != LINE_TREE_DIR) {
return;
}
- string_copy_rev(view->ref, text);
+ string_copy_rev(view->ref, entry->id);
}
static const char *tree_argv[SIZEOF_ARG] = {
tree_argv,
NULL,
tree_read,
- pager_draw,
+ tree_draw,
tree_request,
pager_grep,
tree_select,
if (!commit) {
commit = parse_blame_commit(view, line, &blamed);
string_format(view->ref, "%s %2d%%", view->vid,
- blamed * 100 / view->lines);
+ view->lines ? blamed * 100 / view->lines : 0);
} else if (match_blame_header("author ", &line)) {
string_ncopy(commit->author, line, strlen(line));
if (!blame->commit->has_previous) {
diff_index_argv[1] = "diff";
diff_index_argv[2] = "--no-color";
- diff_index_argv[5] = "--";
- diff_index_argv[6] = "/dev/null";
+ diff_index_argv[6] = "--";
+ diff_index_argv[7] = "/dev/null";
}
if (!prepare_update(diff, diff_index_argv, NULL, FORMAT_DASH)) {
@@ -4358,6 +4561,7 @@ status_run(struct view *view, const char *argv[], char status, enum line_type ty
unmerged = NULL;
if (collapse) {
free(file);
+ file = NULL;
view->lines--;
continue;
}
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 *nameend = strchr(ident, '<');
- char *emailend = strchr(ident, '>');
-
- if (!nameend || !emailend)
- break;
-
+ parse_author_line(line + STRING_SIZE("author "),
+ commit->author, sizeof(commit->author),
+ &commit->time);
update_rev_graph(view, graph);
graph = graph->next;
-
- *nameend = *emailend = 0;
- ident = chomp_string(ident);
- if (!*ident) {
- ident = chomp_string(nameend + 1);
- if (!*ident)
- ident = "Unknown";
- }
-
- string_ncopy(commit->author, ident, strlen(ident));
- view->line[view->lines - 1].dirty = 1;
-
- /* Parse epoch and timezone */
- if (emailend[1] == ' ') {
- char *secs = emailend + 2;
- char *zone = strchr(secs, ' ');
- time_t time = (time_t) atol(secs);
-
- if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
- long tz;
-
- zone++;
- tz = ('0' - zone[1]) * 60 * 60 * 10;
- tz += ('0' - zone[2]) * 60 * 60;
- tz += ('0' - zone[3]) * 60;
- tz += ('0' - zone[4]) * 60;
-
- if (zone[0] == '-')
- tz = -tz;
-
- time -= tz;
- }
-
- gmtime_r(&time, &commit->time);
- }
break;
- }
+
default:
/* Fill in the commit title if it has not already been set. */
if (commit->title[0])
* the option else either "true" or "false" is printed.
* Default to true for the unknown case. */
opt_is_inside_work_tree = strcmp(name, "false") ? TRUE : FALSE;
- } else {
+
+ } else if (*name == '.') {
string_ncopy(opt_cdup, name, namelen);
+
+ } else {
+ string_ncopy(opt_prefix, name, namelen);
}
return OK;
};
const char *rev_parse_argv[] = {
"git", "rev-parse", "--git-dir", "--is-inside-work-tree",
- "--show-cdup", NULL
+ "--show-cdup", "--show-prefix", NULL
};
if (run_io_buf(head_argv, opt_head, sizeof(opt_head))) {