Code

Cleanup exclude rule setup by using ls-files --exclude-standard flag
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index 3802204dc05e6f294e4ecf3fcafbbc42b7c654b8..15a42a436bf83e71cc6c0d4e41897fb7e24a05a5 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -67,6 +67,7 @@ static void report(const char *msg, ...);
 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"
@@ -375,6 +379,7 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
        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"), \
@@ -451,7 +456,7 @@ static bool opt_rev_graph           = FALSE;
 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]       = "";
@@ -470,34 +475,32 @@ 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]     = "";
 
-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);
 
@@ -508,14 +511,13 @@ parse_options(int argc, char *argv[])
                }
 
                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 {
@@ -525,7 +527,7 @@ parse_options(int argc, char *argv[])
        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);
 
@@ -539,11 +541,11 @@ parse_options(int argc, char *argv[])
 
                } 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++] = ' ';
@@ -554,7 +556,7 @@ parse_options(int argc, char *argv[])
 
        opt_cmd[buf_size] = 0;
 
-       return TRUE;
+       return request;
 }
 
 
@@ -779,6 +781,7 @@ static struct keybinding default_keybindings[] = {
        { 'F',          REQ_TOGGLE_REFS },
        { ':',          REQ_PROMPT },
        { 'u',          REQ_STATUS_UPDATE },
+       { '!',          REQ_STATUS_CHECKOUT },
        { 'M',          REQ_STATUS_MERGE },
        { '@',          REQ_STAGE_NEXT },
        { ',',          REQ_TREE_PARENT },
@@ -1100,6 +1103,14 @@ static bool parse_bool(const char *s)
                !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[])
@@ -1145,12 +1156,17 @@ 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;
        }
 
@@ -2382,7 +2398,7 @@ update_view(struct view *view)
        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);
 
@@ -2520,6 +2536,14 @@ open_view(struct view *prev, enum request request, enum open_flags flags)
                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)
 {
@@ -2636,6 +2660,7 @@ view_driver(struct view *view, enum request request)
                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
@@ -2795,11 +2820,6 @@ view_driver(struct view *view, enum request request)
                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);
@@ -2834,12 +2854,10 @@ view_driver(struct view *view, enum request 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
@@ -3677,7 +3695,7 @@ blame_draw(struct view *view, struct line *line, unsigned int lineno)
                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))
@@ -3949,9 +3967,9 @@ error_out:
 #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"
@@ -3968,12 +3986,7 @@ error_out:
 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++)
@@ -3993,33 +4006,16 @@ status_open(struct view *view)
        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
@@ -4346,6 +4342,40 @@ status_update(struct view *view)
        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)
 {
@@ -4357,6 +4387,11 @@ 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').");
@@ -4757,6 +4792,15 @@ append_to_rev_graph(struct rev_graph *graph, chtype symbol)
                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)
 {
@@ -4773,9 +4817,7 @@ 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
@@ -4929,7 +4971,7 @@ main_draw(struct view *view, struct line *line, unsigned int lineno)
                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 &&
@@ -4978,9 +5020,22 @@ main_read(struct view *view, char *line)
        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;
        }
 
@@ -5100,10 +5155,17 @@ main_request(struct view *view, enum request request, struct line *line)
 {
        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;
 }
@@ -5451,6 +5513,58 @@ init_display(void)
        }
 }
 
+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)
 {
@@ -5857,11 +5971,12 @@ main(int argc, char *argv[])
        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"))
@@ -5879,8 +5994,6 @@ main(int argc, char *argv[])
        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)) {
@@ -5911,11 +6024,13 @@ main(int argc, char *argv[])
 
                        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;