index 0aaf2f555417f1a67cd77ffd6114821e479fe259..aec50bca39ff98d3d8358789c6a70f676e2d5bfb 100644 (file)
--- a/tig.c
+++ b/tig.c
#define ENUM_MAP(name, value) { name, STRING_SIZE(name), value }
static bool
-map_enum_do(struct enum_map *map, size_t map_size, int *value, const char *name)
+map_enum_do(const struct enum_map *map, size_t map_size, int *value, const char *name)
{
size_t namelen = strlen(name);
int i;
{
init_io(io, NULL, IO_FD);
io->pipe = *name ? open(name, O_RDONLY) : STDIN_FILENO;
+ if (io->pipe == -1)
+ io->error = errno;
return io->pipe != -1;
}
return io->error;
}
-static bool
+static char *
io_strerror(struct io *io)
{
return strerror(io->error);
const char *help;
};
-static struct request_info req_info[] = {
+static const struct request_info req_info[] = {
#define REQ_GROUP(help) { 0, NULL, 0, (help) },
#define REQ_(req, help) { REQ_##req, (#req), STRING_SIZE(#req), (help) }
REQ_INFO
enum request request;
};
-static struct keybinding default_keybindings[] = {
+static const struct keybinding default_keybindings[] = {
/* View switching */
{ 'm', REQ_VIEW_MAIN },
{ 'd', REQ_VIEW_DIFF },
#undef KEYMAP_
};
-static struct enum_map keymap_table[] = {
+static const struct enum_map keymap_table[] = {
#define KEYMAP_(name) ENUM_MAP(#name, KEYMAP_##name)
KEYMAP_INFO
#undef KEYMAP_
int value;
};
-static struct key key_table[] = {
+static const struct key key_table[] = {
{ "Enter", KEY_RETURN },
{ "Space", ' ' },
{ "Backspace", KEY_BACKSPACE },
buf[pos] = 0;
for (i = 0; i < ARRAY_SIZE(default_keybindings); i++) {
- struct keybinding *keybinding = &default_keybindings[i];
+ const struct keybinding *keybinding = &default_keybindings[i];
if (keybinding->request != request)
continue;
static bool config_errors;
static const char *config_msg;
-static struct enum_map color_map[] = {
+static const struct enum_map color_map[] = {
#define COLOR_MAP(name) ENUM_MAP(#name, COLOR_##name)
COLOR_MAP(DEFAULT),
COLOR_MAP(BLACK),
COLOR_MAP(YELLOW),
};
-static struct enum_map attr_map[] = {
+static const struct enum_map attr_map[] = {
#define ATTR_MAP(name) ENUM_MAP(#name, A_##name)
ATTR_MAP(NORMAL),
ATTR_MAP(BLINK),
info = get_line_info(argv[0]);
if (!info) {
- static struct enum_map obsolete[] = {
+ static const struct enum_map obsolete[] = {
ENUM_MAP("main-delim", LINE_DELIMITER),
ENUM_MAP("main-date", LINE_DATE),
ENUM_MAP("main-author", LINE_AUTHOR),
request = get_request(argv[2]);
if (request == REQ_NONE) {
- static struct enum_map obsolete[] = {
+ static const struct enum_map obsolete[] = {
ENUM_MAP("cherry-pick", REQ_NONE),
ENUM_MAP("screen-resize", REQ_NONE),
ENUM_MAP("tree-parent", REQ_PARENT),
report("%sabling %s", *option ? "En" : "Dis", help);
}
+static void
+maximize_view(struct view *view)
+{
+ memset(display, 0, sizeof(display));
+ current_view = 0;
+ display[current_view] = view;
+ resize_display();
+ redraw_display(FALSE);
+ report("");
+}
+
+
/*
* Navigation
*/
case REQ_MAXIMIZE:
if (displayed_views() == 2)
- open_view(view, VIEW_REQ(view), OPEN_DEFAULT);
+ maximize_view(view);
break;
case REQ_TOGGLE_LINENO:
* followed. */
if (view->parent &&
view->parent->parent != view->parent) {
- memset(display, 0, sizeof(display));
- current_view = 0;
- display[current_view] = view->parent;
+ maximize_view(view->parent);
view->parent = view;
- resize_display();
- redraw_display(FALSE);
- report("");
break;
}
/* Fall-through */
}
static bool
-select_commit_parent(const char *id, char rev[SIZEOF_REV])
+select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
{
char buf[SIZEOF_STR * 4];
const char *revlist_argv[] = {
- "git", "rev-list", "-1", "--parents", id, NULL
+ "git", "rev-list", "-1", "--parents", id, "--", path, NULL
};
int parents;
return FALSE;
} else if (parents == 0) {
- report("The selected commit has no parents");
+ if (path)
+ report("Path '%s' does not exist in the parent", path);
+ else
+ report("The selected commit has no parents");
return FALSE;
}
struct blame {
struct blame_commit *commit;
+ unsigned long lineno;
char text[1];
};
{
struct blame_commit *commit;
struct blame *blame;
- const char *pos = text + SIZEOF_REV - 1;
+ const char *pos = text + SIZEOF_REV - 2;
+ size_t orig_lineno = 0;
size_t lineno;
size_t group;
- if (strlen(text) <= SIZEOF_REV || *pos != ' ')
+ if (strlen(text) <= SIZEOF_REV || pos[1] != ' ')
return NULL;
- if (!parse_number(&pos, &lineno, 1, view->lines) ||
+ if (!parse_number(&pos, &orig_lineno, 1, 9999999) ||
+ !parse_number(&pos, &lineno, 1, view->lines) ||
!parse_number(&pos, &group, 1, view->lines - lineno + 1))
return NULL;
blame = line->data;
blame->commit = commit;
+ blame->lineno = orig_lineno + group - 1;
line->dirty = 1;
}
}
static bool
-check_blame_commit(struct blame *blame)
+check_blame_commit(struct blame *blame, bool check_null_id)
{
if (!blame->commit)
report("Commit data not loaded yet");
- else if (!strcmp(blame->commit->id, NULL_ID))
+ else if (check_null_id && !strcmp(blame->commit->id, NULL_ID))
report("No commit exist for the selected line");
else
return TRUE;
return FALSE;
}
+static void
+setup_blame_parent_line(struct view *view, struct blame *blame)
+{
+ const char *diff_tree_argv[] = {
+ "git", "diff-tree", "-U0", blame->commit->id,
+ "--", blame->commit->filename, NULL
+ };
+ struct io io = {};
+ int parent_lineno = -1;
+ int blamed_lineno = -1;
+ char *line;
+
+ if (!run_io(&io, diff_tree_argv, NULL, IO_RD))
+ return;
+
+ while ((line = io_get(&io, '\n', TRUE))) {
+ if (*line == '@') {
+ char *pos = strchr(line, '+');
+
+ parent_lineno = atoi(line + 4);
+ if (pos)
+ blamed_lineno = atoi(pos + 1);
+
+ } else if (*line == '+' && parent_lineno != -1) {
+ if (blame->lineno == blamed_lineno - 1 &&
+ !strcmp(blame->text, line + 1)) {
+ view->lineno = parent_lineno ? parent_lineno - 1 : 0;
+ break;
+ }
+ blamed_lineno++;
+ }
+ }
+
+ done_io(&io);
+}
+
static enum request
blame_request(struct view *view, enum request request, struct line *line)
{
switch (request) {
case REQ_VIEW_BLAME:
- if (check_blame_commit(blame)) {
+ if (check_blame_commit(blame, TRUE)) {
string_copy(opt_ref, blame->commit->id);
+ string_copy(opt_file, blame->commit->filename);
+ if (blame->lineno)
+ view->lineno = blame->lineno;
open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
}
break;
case REQ_PARENT:
- if (check_blame_commit(blame) &&
- select_commit_parent(blame->commit->id, opt_ref))
+ if (check_blame_commit(blame, TRUE) &&
+ select_commit_parent(blame->commit->id, opt_ref,
+ blame->commit->filename)) {
+ string_copy(opt_file, blame->commit->filename);
+ setup_blame_parent_line(view, blame);
open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
+ }
break;
case REQ_ENTER:
- if (!blame->commit) {
- report("No commit loaded yet");
+ if (!check_blame_commit(blame, FALSE))
break;
- }
if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
!strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
return TRUE;
}
+static enum request
+status_load_error(struct view *view, struct view *stage, const char *path)
+{
+ if (displayed_views() == 2 || display[current_view] != view)
+ maximize_view(view);
+ report("Failed to load '%s': %s", path, io_strerror(&stage->io));
+ return REQ_NONE;
+}
+
static enum request
status_enter(struct view *view, struct line *line)
{
};
if (!prepare_update(stage, no_head_diff_argv, opt_cdup, FORMAT_DASH))
- return REQ_QUIT;
+ return status_load_error(view, stage, newpath);
} else {
const char *index_show_argv[] = {
"git", "diff-index", "--root", "--patch-with-stat",
};
if (!prepare_update(stage, index_show_argv, opt_cdup, FORMAT_DASH))
- return REQ_QUIT;
+ return status_load_error(view, stage, newpath);
}
if (status)
};
if (!prepare_update(stage, files_show_argv, opt_cdup, FORMAT_DASH))
- return REQ_QUIT;
+ return status_load_error(view, stage, newpath);
if (status)
info = "Unstaged changes to %s";
else
}
if (!prepare_update_file(stage, newpath))
- return REQ_QUIT;
+ return status_load_error(view, stage, newpath);
info = "Untracked file %s";
break;
/* After returning the status view has been split to
* show the stage view. No further reloading is
* necessary. */
- status_enter(view, line);
- return REQ_NONE;
+ return status_enter(view, line);
case REQ_REFRESH:
/* Simply reload the view. */
load_refs(void)
{
static const char *ls_remote_argv[SIZEOF_ARG] = {
- "git", "ls-remote", ".", NULL
+ "git", "ls-remote", opt_git_dir, NULL
};
static bool init = FALSE;
return run_io_load(ls_remote_argv, "\t", read_ref);
}
+static void
+set_remote_branch(const char *name, const char *value, size_t valuelen)
+{
+ if (!strcmp(name, ".remote")) {
+ string_ncopy(opt_remote, value, valuelen);
+
+ } else if (*opt_remote && !strcmp(name, ".merge")) {
+ size_t from = strlen(opt_remote);
+
+ if (!prefixcmp(value, "refs/heads/"))
+ value += STRING_SIZE("refs/heads/");
+
+ if (!string_format_from(opt_remote, &from, "/%s", value))
+ opt_remote[0] = 0;
+ }
+}
+
static void
set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
{
@@ -6645,16 +6727,43 @@ set_repo_config_option(char *name, char *value, int (*cmd)(int, const char **))
warn("Option 'tig.%s': %s", name, config_msg);
}
+static void
+set_work_tree(const char *value)
+{
+ char cwd[SIZEOF_STR];
+
+ if (!getcwd(cwd, sizeof(cwd)))
+ die("Failed to get cwd path: %s", strerror(errno));
+ if (chdir(opt_git_dir) < 0)
+ die("Failed to chdir(%s): %s", strerror(errno));
+ if (!getcwd(opt_git_dir, sizeof(opt_git_dir)))
+ die("Failed to get git path: %s", strerror(errno));
+ if (chdir(cwd) < 0)
+ die("Failed to chdir(%s): %s", cwd, strerror(errno));
+ if (chdir(value) < 0)
+ die("Failed to chdir(%s): %s", value, strerror(errno));
+ if (!getcwd(cwd, sizeof(cwd)))
+ die("Failed to get cwd path: %s", strerror(errno));
+ if (setenv("GIT_WORK_TREE", cwd, TRUE) < 0)
+ die("Failed to set GIT_WORK_TREE to '%s'", cwd);
+ if (setenv("GIT_DIR", opt_git_dir, TRUE) < 0)
+ die("Failed to set GIT_DIR to '%s'", opt_git_dir);
+ opt_is_inside_work_tree = TRUE;
+}
+
static int
read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen)
{
if (!strcmp(name, "i18n.commitencoding"))
string_ncopy(opt_encoding, value, valuelen);
- if (!strcmp(name, "core.editor"))
+ else if (!strcmp(name, "core.editor"))
string_ncopy(opt_editor, value, valuelen);
- if (!prefixcmp(name, "tig.color."))
+ else if (!strcmp(name, "core.worktree"))
+ set_work_tree(value);
+
+ else if (!prefixcmp(name, "tig.color."))
set_repo_config_option(name + 10, value, option_color_command);
else if (!prefixcmp(name, "tig.bind."))
@@ -6663,27 +6772,9 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen
else if (!prefixcmp(name, "tig."))
set_repo_config_option(name + 4, value, option_set_command);
- /* 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 (!prefixcmp(value, "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;
- }
+ else if (*opt_head && !prefixcmp(name, "branch.") &&
+ !strncmp(name + 7, opt_head, strlen(opt_head)))
+ set_remote_branch(name + 7 + strlen(opt_head), value, valuelen);
return OK;
}