index 3802204dc05e6f294e4ecf3fcafbbc42b7c654b8..15a42a436bf83e71cc6c0d4e41897fb7e24a05a5 100644 (file)
--- a/tig.c
+++ b/tig.c
static int read_properties(FILE *pipe, const char *separators, int (*read)(char *, size_t, char *, size_t));
static void set_nonblocking_input(bool loading);
static size_t utf8_length(const char *string, int *width, size_t max_width, int *trimmed, bool reserve);
+static bool prompt_yesno(const char *prompt);
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
@@ -125,8 +126,11 @@ static size_t utf8_length(const char *string, int *width, size_t max_width, int
#define TIG_LOG_CMD \
"git log --no-color --cc --stat -n100 %s 2>/dev/null"
+#define TIG_MAIN_BASE \
+ "git log --no-color --pretty=raw --parents --topo-order"
+
#define TIG_MAIN_CMD \
- "git log --no-color --topo-order --parents --boundary --pretty=raw %s 2>/dev/null"
+ TIG_MAIN_BASE " %s 2>/dev/null"
#define TIG_TREE_CMD \
"git ls-tree %s %s"
REQ_(TOGGLE_REV_GRAPH, "Toggle revision graph visualization"), \
REQ_(TOGGLE_REFS, "Toggle reference display (tags/branches)"), \
REQ_(STATUS_UPDATE, "Update file status"), \
+ REQ_(STATUS_CHECKOUT, "Checkout file"), \
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"), \
static bool opt_show_refs = TRUE;
static int opt_num_interval = NUMBER_INTERVAL;
static int opt_tab_size = TAB_SIZE;
-static enum request opt_request = REQ_VIEW_MAIN;
+static int opt_author_cols = AUTHOR_COLS-1;
static char opt_cmd[SIZEOF_STR] = "";
static char opt_path[SIZEOF_STR] = "";
static char opt_file[SIZEOF_STR] = "";
static signed char opt_is_inside_work_tree = -1; /* set to TRUE or FALSE */
static char opt_editor[SIZEOF_STR] = "";
-static bool
+static enum request
parse_options(int argc, char *argv[])
{
+ enum request request = REQ_VIEW_MAIN;
size_t buf_size;
char *subcommand;
bool seen_dashdash = FALSE;
int i;
if (!isatty(STDIN_FILENO)) {
- opt_request = REQ_VIEW_PAGER;
opt_pipe = stdin;
- return TRUE;
+ return REQ_VIEW_PAGER;
}
if (argc <= 1)
- return TRUE;
+ return REQ_VIEW_MAIN;
subcommand = argv[1];
if (!strcmp(subcommand, "status") || !strcmp(subcommand, "-S")) {
- opt_request = REQ_VIEW_STATUS;
if (!strcmp(subcommand, "-S"))
warn("`-S' has been deprecated; use `tig status' instead");
if (argc > 2)
warn("ignoring arguments after `%s'", subcommand);
- return TRUE;
+ return REQ_VIEW_STATUS;
} else if (!strcmp(subcommand, "blame")) {
- opt_request = REQ_VIEW_BLAME;
if (argc <= 2 || argc > 4)
die("invalid number of options to blame\n\n%s", usage);
}
string_ncopy(opt_file, argv[i], strlen(argv[i]));
- return TRUE;
+ return REQ_VIEW_BLAME;
} else if (!strcmp(subcommand, "show")) {
- opt_request = REQ_VIEW_DIFF;
+ request = REQ_VIEW_DIFF;
} else if (!strcmp(subcommand, "log") || !strcmp(subcommand, "diff")) {
- opt_request = subcommand[0] == 'l'
- ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
+ request = subcommand[0] == 'l' ? REQ_VIEW_LOG : REQ_VIEW_DIFF;
warn("`tig %s' has been deprecated", subcommand);
} else {
if (!subcommand)
/* XXX: This is vulnerable to the user overriding
* options required for the main view parser. */
- string_copy(opt_cmd, "git log --no-color --pretty=raw --boundary --parents");
+ string_copy(opt_cmd, TIG_MAIN_BASE);
else
string_format(opt_cmd, "git %s", subcommand);
} else if (!strcmp(opt, "-v") || !strcmp(opt, "--version")) {
printf("tig version %s\n", TIG_VERSION);
- return FALSE;
+ return REQ_NONE;
} else if (!strcmp(opt, "-h") || !strcmp(opt, "--help")) {
printf("%s\n", usage);
- return FALSE;
+ return REQ_NONE;
}
opt_cmd[buf_size++] = ' ';
opt_cmd[buf_size] = 0;
- return TRUE;
+ return request;
}
{ 'F', REQ_TOGGLE_REFS },
{ ':', REQ_PROMPT },
{ 'u', REQ_STATUS_UPDATE },
+ { '!', REQ_STATUS_CHECKOUT },
{ 'M', REQ_STATUS_MERGE },
{ '@', REQ_STAGE_NEXT },
{ ',', REQ_TREE_PARENT },
!strcmp(s, "yes")) ? TRUE : FALSE;
}
+static int
+parse_int(const char *s, int default_value, int min, int max)
+{
+ int value = atoi(s);
+
+ return (value < min || value > max) ? default_value : value;
+}
+
/* Wants: name = value */
static int
option_set_command(int argc, char *argv[])
}
if (!strcmp(argv[0], "line-number-interval")) {
- opt_num_interval = atoi(argv[2]);
+ opt_num_interval = parse_int(argv[2], opt_num_interval, 1, 1024);
+ return OK;
+ }
+
+ if (!strcmp(argv[0], "author-width")) {
+ opt_author_cols = parse_int(argv[2], opt_author_cols, 0, 1024);
return OK;
}
if (!strcmp(argv[0], "tab-size")) {
- opt_tab_size = atoi(argv[2]);
+ opt_tab_size = parse_int(argv[2], opt_tab_size, 1, 1024);
return OK;
}
update_view_title(view);
check_pipe:
- if (ferror(view->pipe)) {
+ if (ferror(view->pipe) && errno != 0) {
report("Failed to read: %s", strerror(errno));
end_update(view, TRUE);
update_view_title(view);
}
+static void
+run_confirm(const char *cmd, const char *prompt)
+{
+ if (prompt_yesno(prompt)) {
+ system(cmd);
+ }
+}
+
static void
open_external_viewer(const char *cmd)
{
open_run_request(request);
/* FIXME: When all views can refresh always do this. */
if (view == VIEW(REQ_VIEW_STATUS) ||
+ view == VIEW(REQ_VIEW_MAIN) ||
view == VIEW(REQ_VIEW_STAGE))
request = REQ_REFRESH;
else
redraw_display();
break;
- case REQ_PROMPT:
- /* Always reload^Wrerun commands from the prompt. */
- open_view(view, opt_request, OPEN_RELOAD);
- break;
-
case REQ_SEARCH:
case REQ_SEARCH_BACK:
search_view(view, request);
report("Nothing to edit");
break;
-
case REQ_ENTER:
report("Nothing to enter");
break;
-
case REQ_VIEW_CLOSE:
/* XXX: Mark closed views by letting view->parent point to the
* view itself. Parents to closed view should never be
return TRUE;
if (opt_author &&
- draw_field(view, LINE_MAIN_AUTHOR, author, AUTHOR_COLS, TRUE))
+ draw_field(view, LINE_MAIN_AUTHOR, author, opt_author_cols, TRUE))
return TRUE;
if (draw_field(view, LINE_BLAME_ID, id, ID_COLS, FALSE))
#define STATUS_DIFF_INDEX_CMD "git diff-index -z --diff-filter=ACDMRTXB --cached -M HEAD"
#define STATUS_DIFF_FILES_CMD "git diff-files -z"
#define STATUS_LIST_OTHER_CMD \
- "git ls-files -z --others --exclude-per-directory=.gitignore"
+ "git ls-files -z --others --exclude-standard"
#define STATUS_LIST_NO_HEAD_CMD \
- "git ls-files -z --cached --exclude-per-directory=.gitignore"
+ "git ls-files -z --cached --exclude-standard"
#define STATUS_DIFF_INDEX_SHOW_CMD \
"git diff-index --root --patch-with-stat -C -M --cached HEAD -- %s %s 2>/dev/null"
static bool
status_open(struct view *view)
{
- struct stat statbuf;
- char exclude[SIZEOF_STR];
- char indexcmd[SIZEOF_STR] = STATUS_DIFF_INDEX_CMD;
- char othercmd[SIZEOF_STR] = STATUS_LIST_OTHER_CMD;
unsigned long prev_lineno = view->lineno;
- char indexstatus = 0;
size_t i;
for (i = 0; i < view->lines; i++)
else if (!string_format(status_onbranch, "On branch %s", opt_head))
return FALSE;
- if (opt_no_head) {
- string_copy(indexcmd, STATUS_LIST_NO_HEAD_CMD);
- indexstatus = 'A';
- }
+ system("git update-index -q --refresh >/dev/null 2>/dev/null");
- if (!string_format(exclude, "%s/info/exclude", opt_git_dir))
+ if (opt_no_head &&
+ !status_run(view, STATUS_LIST_NO_HEAD_CMD, 'A', LINE_STAT_STAGED))
+ return FALSE;
+ else if (!status_run(view, STATUS_DIFF_INDEX_CMD, 0, LINE_STAT_STAGED))
return FALSE;
- if (stat(exclude, &statbuf) >= 0) {
- size_t cmdsize = strlen(othercmd);
-
- if (!string_format_from(othercmd, &cmdsize, " %s", "--exclude-from=") ||
- sq_quote(othercmd, cmdsize, exclude) >= sizeof(othercmd))
- return FALSE;
-
- cmdsize = strlen(indexcmd);
- if (opt_no_head &&
- (!string_format_from(indexcmd, &cmdsize, " %s", "--exclude-from=") ||
- sq_quote(indexcmd, cmdsize, exclude) >= sizeof(indexcmd)))
- return FALSE;
- }
-
- system("git update-index -q --refresh >/dev/null 2>/dev/null");
-
- if (!status_run(view, indexcmd, indexstatus, LINE_STAT_STAGED) ||
- !status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
- !status_run(view, othercmd, '?', LINE_STAT_UNTRACKED))
+ if (!status_run(view, STATUS_DIFF_FILES_CMD, 0, LINE_STAT_UNSTAGED) ||
+ !status_run(view, STATUS_LIST_OTHER_CMD, '?', LINE_STAT_UNTRACKED))
return FALSE;
/* If all went well restore the previous line number to stay in
return TRUE;
}
+static bool
+status_checkout(struct view *view)
+{
+ struct line *line = &view->line[view->lineno];
+
+ assert(view->lines);
+
+ if (!line->data || line->type != LINE_STAT_UNSTAGED) {
+ /* This should work even for the "On branch" line. */
+ if (line < view->line + view->lines && !line[1].data) {
+ report("Nothing to checkout");
+ } else if (line->type == LINE_STAT_UNTRACKED) {
+ report("Cannot checkout untracked files");
+ } else if (line->type == LINE_STAT_STAGED) {
+ report("Cannot checkout staged files");
+ } else {
+ report("Cannot checkout multiple files");
+ }
+ return FALSE;
+
+ } else {
+ struct status *status = line->data;
+ char cmd[SIZEOF_STR];
+ char file_sq[SIZEOF_STR];
+
+ if (sq_quote(file_sq, 0, status->old.name) < sizeof(file_sq) &&
+ string_format(cmd, "git checkout %s%s", opt_cdup, file_sq)) {
+ run_confirm(cmd, "Are you sure you want to overwrite any changes?");
+ }
+
+ return TRUE;
+ }
+}
+
static enum request
status_request(struct view *view, enum request request, struct line *line)
{
return REQ_NONE;
break;
+ case REQ_STATUS_CHECKOUT:
+ if (!status_checkout(view))
+ return REQ_NONE;
+ break;
+
case REQ_STATUS_MERGE:
if (!status || status->status != 'U') {
report("Merging only possible for files with unmerged status ('U').");
commit->graph[commit->graph_size++] = symbol;
}
+static void
+clear_rev_graph(struct rev_graph *graph)
+{
+ graph->boundary = 0;
+ graph->size = graph->pos = 0;
+ graph->commit = NULL;
+ memset(graph->parents, 0, sizeof(*graph->parents));
+}
+
static void
done_rev_graph(struct rev_graph *graph)
{
}
}
- graph->size = graph->pos = 0;
- graph->commit = NULL;
- memset(graph->parents, 0, sizeof(*graph->parents));
+ clear_rev_graph(graph);
}
static void
return TRUE;
if (opt_author &&
- draw_field(view, LINE_MAIN_AUTHOR, commit->author, AUTHOR_COLS, TRUE))
+ draw_field(view, LINE_MAIN_AUTHOR, commit->author, opt_author_cols, TRUE))
return TRUE;
if (opt_rev_graph && commit->graph_size &&
struct commit *commit;
if (!line) {
+ int i;
+
if (!view->lines && !view->parent)
die("No revisions match the given arguments.");
+ if (view->lines > 0) {
+ commit = view->line[view->lines - 1].data;
+ if (!*commit->author) {
+ view->lines--;
+ free(commit);
+ graph->commit = NULL;
+ }
+ }
update_rev_graph(graph);
+
+ for (i = 0; i < ARRAY_SIZE(graph_stacks); i++)
+ clear_rev_graph(&graph_stacks[i]);
return TRUE;
}
{
enum open_flags flags = display[0] == view ? OPEN_SPLIT : OPEN_DEFAULT;
- if (request == REQ_ENTER)
+ switch (request) {
+ case REQ_ENTER:
open_view(view, REQ_VIEW_DIFF, flags);
- else
+ break;
+ case REQ_REFRESH:
+ string_copy(opt_cmd, view->cmd);
+ open_view(view, REQ_VIEW_MAIN, OPEN_RELOAD);
+ break;
+ default:
return request;
+ }
return REQ_NONE;
}
}
}
+static bool
+prompt_yesno(const char *prompt)
+{
+ enum { WAIT, STOP, CANCEL } status = WAIT;
+ bool answer = FALSE;
+
+ while (status == WAIT) {
+ struct view *view;
+ int i, key;
+
+ input_mode = TRUE;
+
+ foreach_view (view, i)
+ update_view(view);
+
+ input_mode = FALSE;
+
+ mvwprintw(status_win, 0, 0, "%s [Yy]/[Nn]", prompt);
+ wclrtoeol(status_win);
+
+ /* Refresh, accept single keystroke of input */
+ key = wgetch(status_win);
+ switch (key) {
+ case ERR:
+ break;
+
+ case 'y':
+ case 'Y':
+ answer = TRUE;
+ status = STOP;
+ break;
+
+ case KEY_ESC:
+ case KEY_RETURN:
+ case KEY_ENTER:
+ case KEY_BACKSPACE:
+ case 'n':
+ case 'N':
+ case '\n':
+ default:
+ answer = FALSE;
+ status = CANCEL;
+ }
+ }
+
+ /* Clear the status window */
+ status_empty = FALSE;
+ report("");
+
+ return answer;
+}
+
static char *
read_prompt(const char *prompt)
{
if (load_git_config() == ERR)
die("Failed to load repo config.");
- if (!parse_options(argc, argv))
+ request = parse_options(argc, argv);
+ if (request == REQ_NONE)
return 0;
/* Require a git repository unless when running in pager mode. */
- if (!opt_git_dir[0] && opt_request != REQ_VIEW_PAGER)
+ if (!opt_git_dir[0] && request != REQ_VIEW_PAGER)
die("Not a git repository");
if (*opt_encoding && strcasecmp(opt_encoding, "UTF-8"))
for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
view->cmd_env = getenv(view->cmd_env);
- request = opt_request;
-
init_display();
while (view_driver(display[current_view], request)) {
if (cmd && string_format(opt_cmd, "git %s", cmd)) {
if (strncmp(cmd, "show", 4) && isspace(cmd[4])) {
- opt_request = REQ_VIEW_DIFF;
+ request = REQ_VIEW_DIFF;
} else {
- opt_request = REQ_VIEW_PAGER;
+ request = REQ_VIEW_PAGER;
}
- break;
+
+ /* Always reload^Wrerun commands from the prompt. */
+ open_view(view, request, OPEN_RELOAD);
}
request = REQ_NONE;