index f52c8a4505d1c894f83b736b2384961d73f663df..3fce6f086685395490beacd03f7afb968c5c7670 100644 (file)
--- a/tig.c
+++ b/tig.c
static struct ref **get_refs(const char *id);
+enum format_flags {
+ FORMAT_ALL, /* Perform replacement in all arguments. */
+ FORMAT_DASH, /* Perform replacement up until "--". */
+ FORMAT_NONE /* No replacement should be performed. */
+};
+
+static bool format_command(char dst[], const char *src[], enum format_flags flags);
+static bool format_argv(const char *dst[], const char *src[], enum format_flags flags);
+
struct int_map {
const char *name;
int namelen;
return bufsize;
}
+static bool
+argv_from_string(const char *argv[SIZEOF_ARG], int *argc, char *cmd)
+{
+ int valuelen;
+
+ while (*cmd && *argc < SIZEOF_ARG && (valuelen = strcspn(cmd, " \t"))) {
+ bool advance = cmd[valuelen] != 0;
+
+ cmd[valuelen] = 0;
+ argv[(*argc)++] = chomp_string(cmd);
+ cmd += valuelen + advance;
+ }
+
+ if (*argc < SIZEOF_ARG)
+ argv[*argc] = NULL;
+ return *argc < SIZEOF_ARG;
+}
+
+
+/*
+ * Executing external commands.
+ */
+
+enum io_type {
+ IO_FD, /* File descriptor based IO. */
+ IO_RD, /* Read only fork+exec IO. */
+ IO_WR, /* Write only fork+exec IO. */
+};
+
+struct io {
+ enum io_type type; /* The requested type of pipe. */
+ FILE *pipe; /* Pipe for reading or writing. */
+ int error; /* Error status. */
+ char sh[SIZEOF_STR]; /* Shell command buffer. */
+ char *buf; /* Read/write buffer. */
+ size_t bufalloc; /* Allocated buffer size. */
+};
+
+static void
+reset_io(struct io *io)
+{
+ io->pipe = NULL;
+ io->buf = NULL;
+ io->bufalloc = 0;
+ io->error = 0;
+}
+
+static void
+init_io(struct io *io, enum io_type type)
+{
+ reset_io(io);
+ io->type = type;
+}
+
+static bool
+init_io_fd(struct io *io, FILE *pipe)
+{
+ init_io(io, IO_FD);
+ io->pipe = pipe;
+ return io->pipe != NULL;
+}
+
+static bool
+done_io(struct io *io)
+{
+ free(io->buf);
+ if (io->type == IO_FD)
+ fclose(io->pipe);
+ else
+ pclose(io->pipe);
+ reset_io(io);
+ return TRUE;
+}
+
+static bool
+start_io(struct io *io)
+{
+ io->pipe = popen(io->sh, io->type == IO_RD ? "r" : "w");
+ return io->pipe != NULL;
+}
+
+static bool
+run_io(struct io *io, enum io_type type, const char *cmd)
+{
+ init_io(io, type);
+ string_ncopy(io->sh, cmd, strlen(cmd));
+ return start_io(io);
+}
+
+static bool
+run_io_format(struct io *io, const char *cmd, ...)
+{
+ va_list args;
+
+ va_start(args, cmd);
+ init_io(io, IO_RD);
+
+ if (vsnprintf(io->sh, sizeof(io->sh), cmd, args) >= sizeof(io->sh))
+ io->sh[0] = 0;
+ va_end(args);
+
+ return io->sh[0] ? start_io(io) : FALSE;
+}
+
+static bool
+io_eof(struct io *io)
+{
+ return feof(io->pipe);
+}
+
+static int
+io_error(struct io *io)
+{
+ return io->error;
+}
+
+static bool
+io_strerror(struct io *io)
+{
+ return strerror(io->error);
+}
+
+static char *
+io_gets(struct io *io)
+{
+ if (!io->buf) {
+ io->buf = malloc(BUFSIZ);
+ if (!io->buf)
+ return NULL;
+ io->bufalloc = BUFSIZ;
+ }
+
+ if (!fgets(io->buf, io->bufalloc, io->pipe)) {
+ if (ferror(io->pipe))
+ io->error = errno;
+ return NULL;
+ }
+
+ return io->buf;
+}
+
/*
* User requests
struct run_request {
enum keymap keymap;
int key;
- char cmd[SIZEOF_STR];
+ const char *argv[SIZEOF_ARG];
};
static struct run_request *run_request;
add_run_request(enum keymap keymap, int key, int argc, const char **argv)
{
struct run_request *req;
- char cmd[SIZEOF_STR];
- size_t bufpos;
- for (bufpos = 0; argc > 0; argc--, argv++)
- if (!string_format_from(cmd, &bufpos, "%s ", *argv))
- return REQ_NONE;
+ if (argc >= ARRAY_SIZE(req->argv) - 1)
+ return REQ_NONE;
req = realloc(run_request, (run_requests + 1) * sizeof(*run_request));
if (!req)
return REQ_NONE;
run_request = req;
- req = &run_request[run_requests++];
- string_copy(req->cmd, cmd);
+ req = &run_request[run_requests];
req->keymap = keymap;
req->key = key;
+ req->argv[0] = NULL;
- return REQ_NONE + run_requests;
+ if (!format_argv(req->argv, argv, FORMAT_NONE))
+ return REQ_NONE;
+
+ return REQ_NONE + ++run_requests;
}
static struct run_request *
static void
add_builtin_run_requests(void)
{
+ const char *cherry_pick[] = { "git", "cherry-pick", "%(commit)", NULL };
+ const char *gc[] = { "git", "gc", NULL };
struct {
enum keymap keymap;
int key;
- const char *argv[1];
+ int argc;
+ const char **argv;
} reqs[] = {
- { KEYMAP_MAIN, 'C', { "git cherry-pick %(commit)" } },
- { KEYMAP_GENERIC, 'G', { "git gc" } },
+ { KEYMAP_MAIN, 'C', ARRAY_SIZE(cherry_pick) - 1, cherry_pick },
+ { KEYMAP_GENERIC, 'G', ARRAY_SIZE(gc) - 1, gc },
};
int i;
for (i = 0; i < ARRAY_SIZE(reqs); i++) {
enum request req;
- req = add_run_request(reqs[i].keymap, reqs[i].key, 1, reqs[i].argv);
+ req = add_run_request(reqs[i].keymap, reqs[i].key, reqs[i].argc, reqs[i].argv);
if (req != REQ_NONE)
add_keybinding(reqs[i].keymap, req, reqs[i].key);
}
set_option(const char *opt, char *value)
{
const char *argv[SIZEOF_ARG];
- int valuelen;
int argc = 0;
- /* Tokenize */
- while (argc < ARRAY_SIZE(argv) && (valuelen = strcspn(value, " \t"))) {
- argv[argc++] = value;
- value += valuelen;
-
- /* Nothing more to tokenize or last available token. */
- if (!*value || argc >= ARRAY_SIZE(argv))
- break;
-
- *value++ = 0;
- while (isspace(*value))
- value++;
+ if (!argv_from_string(argv, &argc, value)) {
+ config_msg = "Too many option arguments";
+ return ERR;
}
if (!strcmp(opt, "color"))
enum keymap keymap; /* What keymap does this view have */
bool git_dir; /* Whether the view requires a git directory. */
- char cmd[SIZEOF_STR]; /* Command buffer */
char ref[SIZEOF_REF]; /* Hovered commit reference */
char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
unsigned long col; /* Column when drawing. */
/* Loading */
- FILE *pipe;
+ struct io io;
+ struct io *pipe;
time_t start_time;
};
view->vid[0] = 0;
}
+static void
+free_argv(const char *argv[])
+{
+ int argc;
+
+ for (argc = 0; argv[argc]; argc++)
+ free((void *) argv[argc]);
+}
+
+static bool
+format_argv(const char *dst_argv[], const char *src_argv[], enum format_flags flags)
+{
+ char buf[SIZEOF_STR];
+ int argc;
+ bool noreplace = flags == FORMAT_NONE;
+
+ free_argv(dst_argv);
+
+ for (argc = 0; src_argv[argc]; argc++) {
+ const char *arg = src_argv[argc];
+ size_t bufpos = 0;
+
+ while (arg) {
+ char *next = strstr(arg, "%(");
+ int len = next - arg;
+ const char *value;
+
+ if (!next || noreplace) {
+ if (flags == FORMAT_DASH && !strcmp(arg, "--"))
+ noreplace = TRUE;
+ len = strlen(arg);
+ value = "";
+
+ } else if (!prefixcmp(next, "%(directory)")) {
+ value = opt_path;
+
+ } else if (!prefixcmp(next, "%(file)")) {
+ value = opt_file;
+
+ } else if (!prefixcmp(next, "%(ref)")) {
+ value = *opt_ref ? opt_ref : "HEAD";
+
+ } else if (!prefixcmp(next, "%(head)")) {
+ value = ref_head;
+
+ } else if (!prefixcmp(next, "%(commit)")) {
+ value = ref_commit;
+
+ } else if (!prefixcmp(next, "%(blob)")) {
+ value = ref_blob;
+
+ } else {
+ report("Unknown replacement: `%s`", next);
+ return FALSE;
+ }
+
+ if (!string_format_from(buf, &bufpos, "%.*s%s", len, arg, value))
+ return FALSE;
+
+ arg = next && !noreplace ? strchr(next, ')') + 1 : NULL;
+ }
+
+ dst_argv[argc] = strdup(buf);
+ if (!dst_argv[argc])
+ break;
+ }
+
+ dst_argv[argc] = NULL;
+
+ return src_argv[argc] == NULL;
+}
+
+static bool
+format_command(char dst[], const char *src_argv[], enum format_flags flags)
+{
+ const char *dst_argv[SIZEOF_ARG * 2] = { NULL };
+ int bufsize = 0;
+ int argc;
+
+ if (!format_argv(dst_argv, src_argv, flags)) {
+ free_argv(dst_argv);
+ return FALSE;
+ }
+
+ for (argc = 0; dst_argv[argc] && bufsize < SIZEOF_STR; argc++) {
+ if (bufsize > 0)
+ dst[bufsize++] = ' ';
+ bufsize = sq_quote(dst, bufsize, dst_argv[argc]);
+ }
+
+ if (bufsize < SIZEOF_STR)
+ dst[bufsize] = 0;
+ free_argv(dst_argv);
+
+ return src_argv[argc] == NULL && bufsize < SIZEOF_STR;
+}
+
static void
end_update(struct view *view, bool force)
{
if (!force)
return;
set_nonblocking_input(FALSE);
- if (view->pipe == stdin)
- fclose(view->pipe);
- else
- pclose(view->pipe);
+ done_io(view->pipe);
view->pipe = NULL;
}
set_nonblocking_input(TRUE);
reset_view(view);
string_copy_rev(view->vid, vid);
+ view->pipe = &view->io;
view->start_time = time(NULL);
}
static bool
begin_update(struct view *view, bool refresh)
{
- if (opt_cmd[0]) {
- string_copy(view->cmd, opt_cmd);
- opt_cmd[0] = 0;
+ if (init_io_fd(&view->io, opt_pipe)) {
+ opt_pipe = NULL;
+
+ } else if (opt_cmd[0]) {
+ if (!run_io(&view->io, IO_RD, opt_cmd))
+ return FALSE;
/* When running random commands, initially show the
* command in the title. However, it maybe later be
* overwritten if a commit line is selected. */
if (view == VIEW(REQ_VIEW_PAGER))
- string_copy(view->ref, view->cmd);
+ string_copy(view->ref, opt_cmd);
else
view->ref[0] = 0;
+ opt_cmd[0] = 0;
+
+ } else if (refresh) {
+ if (!start_io(&view->io))
+ return FALSE;
} else if (view == VIEW(REQ_VIEW_TREE)) {
const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
else if (sq_quote(path, 0, opt_path) >= sizeof(path))
return FALSE;
- if (!string_format(view->cmd, format, view->id, path))
+ if (!run_io_format(&view->io, format, view->id, path))
return FALSE;
- } else if (!refresh) {
+ } else {
const char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
const char *id = view->id;
- if (!string_format(view->cmd, format, id, id, id, id, id))
+ if (!run_io_format(&view->io, format, id, id, id, id, id))
return FALSE;
/* Put the current ref_* value to the view title ref
string_copy_rev(view->ref, view->id);
}
- /* Special case for the pager view. */
- if (opt_pipe) {
- view->pipe = opt_pipe;
- opt_pipe = NULL;
- } else {
- view->pipe = popen(view->cmd, "r");
- }
-
- if (!view->pipe)
- return FALSE;
-
setup_update(view, view->id);
return TRUE;
static bool
update_view(struct view *view)
{
- char in_buffer[BUFSIZ];
char out_buffer[BUFSIZ * 2];
char *line;
/* The number of lines to read. If too low it will cause too much
if (!realloc_lines(view, view->lines + lines))
goto alloc_error;
- while ((line = fgets(in_buffer, sizeof(in_buffer), view->pipe))) {
+ while ((line = io_gets(view->pipe))) {
size_t linelen = strlen(line);
if (linelen)
}
}
- if (ferror(view->pipe) && errno != 0) {
- report("Failed to read: %s", strerror(errno));
+ if (io_error(view->pipe)) {
+ report("Failed to read: %s", io_strerror(view->pipe));
end_update(view, TRUE);
- } else if (feof(view->pipe)) {
+ } else if (io_eof(view->pipe)) {
report("");
end_update(view, FALSE);
}
{
struct run_request *req = get_run_request(request);
char buf[SIZEOF_STR * 2];
- size_t bufpos;
- char *cmd;
if (!req) {
report("Unknown run request");
return;
}
- bufpos = 0;
- cmd = req->cmd;
-
- while (cmd) {
- char *next = strstr(cmd, "%(");
- int len = next - cmd;
- char *value;
-
- if (!next) {
- len = strlen(cmd);
- value = "";
-
- } else if (!strncmp(next, "%(head)", 7)) {
- value = ref_head;
-
- } else if (!strncmp(next, "%(commit)", 9)) {
- value = ref_commit;
-
- } else if (!strncmp(next, "%(blob)", 7)) {
- value = ref_blob;
-
- } else {
- report("Unknown replacement in run request: `%s`", req->cmd);
- return;
- }
-
- if (!string_format_from(buf, &bufpos, "%.*s%s", len, cmd, value))
- return;
-
- if (next)
- next = strchr(next, ')') + 1;
- cmd = next;
- }
-
- open_external_viewer(buf);
+ if (format_command(buf, req->argv, FORMAT_ALL))
+ open_external_viewer(buf);
}
/*
for (i = 0; i < run_requests; i++) {
struct run_request *req = get_run_request(REQ_NONE + i + 1);
const char *key;
+ char cmd[SIZEOF_STR];
+ size_t bufpos;
+ int argc;
if (!req)
continue;
if (!*key)
key = "(no key defined)";
+ for (bufpos = 0, argc = 0; req->argv[argc]; argc++)
+ if (!string_format_from(cmd, &bufpos, "%s%s",
+ argc ? " " : "", req->argv[argc]))
+ return REQ_NONE;
+
if (!string_format(buf, " %-10s %-14s `%s`",
- keymap_table[req->keymap].name,
- key, req->cmd))
+ keymap_table[req->keymap].name, key, cmd))
continue;
add_line_text(view, buf, LINE_DEFAULT);
if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
return FALSE;
- if (*opt_ref || !(view->pipe = fopen(opt_file, "r"))) {
+ if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
const char *id = *opt_ref ? ref : "HEAD";
- if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, id, path) ||
- !(view->pipe = popen(view->cmd, "r")))
+ if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
return FALSE;
}
if (!line) {
char ref[SIZEOF_STR] = "";
char path[SIZEOF_STR];
- FILE *pipe = NULL;
+ struct io io = {};
if (view->lines == 0 && !view->parent)
die("No blame exist for %s", view->vid);
if (view->lines == 0 ||
sq_quote(path, 0, opt_file) >= sizeof(path) ||
(*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
- !string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path) ||
- !(pipe = popen(view->cmd, "r"))) {
+ !run_io_format(&io, BLAME_INCREMENTAL_CMD, ref, path)) {
report("Failed to load blame data");
return TRUE;
}
- fclose(view->pipe);
- view->pipe = pipe;
+ done_io(view->pipe);
+ view->io = io;
*read_file = FALSE;
return FALSE;
struct blame *blame = line->data;
switch (request) {
+ case REQ_VIEW_BLAME:
+ if (!blame->commit || !strcmp(blame->commit->id, NULL_ID)) {
+ report("Commit ID unknown");
+ break;
+ }
+ string_copy(opt_ref, blame->commit->id);
+ open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
+ return request;
+
case REQ_ENTER:
if (!blame->commit) {
report("No commit loaded yet");
break;
}
+ if (view_is_displayed(VIEW(REQ_VIEW_DIFF)) &&
+ !strcmp(blame->commit->id, VIEW(REQ_VIEW_DIFF)->ref))
+ break;
+
if (!strcmp(blame->commit->id, NULL_ID)) {
char path[SIZEOF_STR];