Code

stage: add request handler supporting file edits and chunk staging
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index 49848d67b459a81abf1edaaa8a935dd3535ea44d..420a13b886afbcae5bd29676a2ef34c9bca7d96b 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -11,8 +11,8 @@
  * GNU General Public License for more details.
  */
 
-#ifndef VERSION
-#define VERSION "unknown-version"
+#ifndef TIG_VERSION
+#define TIG_VERSION "unknown-version"
 #endif
 
 #ifndef DEBUG
@@ -40,6 +40,8 @@
 
 #include <curses.h>
 
+#include "config.h"
+
 #if __GNUC__ >= 3
 #define __NORETURN __attribute__((__noreturn__))
 #else
@@ -90,6 +92,10 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 
 #define        SCALE_SPLIT_VIEW(height)        ((height) * 2 / 3)
 
+#ifndef GIT_CONFIG
+#define "git config"
+#endif
+
 #define TIG_LS_REMOTE \
        "git ls-remote $(git rev-parse --git-dir) 2>/dev/null"
 
@@ -112,6 +118,7 @@ static size_t utf8_length(const char *string, size_t max_width, int *coloffset,
 #define TIG_HELP_CMD   ""
 #define TIG_PAGER_CMD  ""
 #define TIG_STATUS_CMD ""
+#define TIG_STAGE_CMD  ""
 
 /* Some ascii-shorthands fitted into the ncurses namespace. */
 #define KEY_TAB                '\t'
@@ -299,6 +306,7 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
        REQ_(VIEW_HELP,         "Show help page"), \
        REQ_(VIEW_PAGER,        "Show pager view"), \
        REQ_(VIEW_STATUS,       "Show status view"), \
+       REQ_(VIEW_STAGE,        "Show stage view"), \
        \
        REQ_GROUP("View manipulation") \
        REQ_(ENTER,             "Enter current line and scroll"), \
@@ -390,7 +398,7 @@ get_request(const char *name)
  */
 
 static const char usage[] =
-"tig " VERSION " (" __DATE__ ")\n"
+"tig " TIG_VERSION " (" __DATE__ ")\n"
 "\n"
 "Usage: tig [options]\n"
 "   or: tig [options] [--] [git log options]\n"
@@ -516,7 +524,7 @@ parse_options(int argc, char *argv[])
                }
 
                if (check_option(opt, 'v', "version", OPT_NONE)) {
-                       printf("tig version %s\n", VERSION);
+                       printf("tig version %s\n", TIG_VERSION);
                        return FALSE;
                }
 
@@ -729,6 +737,7 @@ static struct keybinding default_keybindings[] = {
        { 'p',          REQ_VIEW_PAGER },
        { 'h',          REQ_VIEW_HELP },
        { 'S',          REQ_VIEW_STATUS },
+       { 'c',          REQ_VIEW_STAGE },
 
        /* View manipulation */
        { 'q',          REQ_VIEW_CLOSE },
@@ -784,7 +793,8 @@ static struct keybinding default_keybindings[] = {
        KEYMAP_(BLOB), \
        KEYMAP_(PAGER), \
        KEYMAP_(HELP), \
-       KEYMAP_(STATUS)
+       KEYMAP_(STATUS), \
+       KEYMAP_(STAGE)
 
 enum keymap {
 #define KEYMAP_(name) KEYMAP_##name
@@ -1280,6 +1290,7 @@ static struct view_ops tree_ops;
 static struct view_ops blob_ops;
 static struct view_ops help_ops;
 static struct view_ops status_ops;
+static struct view_ops stage_ops;
 
 #define VIEW_STR(name, cmd, env, ref, ops, map) \
        { name, cmd, #env, ref, ops, map}
@@ -1297,6 +1308,7 @@ static struct view views[] = {
        VIEW_(HELP,   "help",   &help_ops,   ""),
        VIEW_(PAGER,  "pager",  &pager_ops,  "stdin"),
        VIEW_(STATUS, "status", &status_ops, ""),
+       VIEW_(STAGE,  "stage",  &stage_ops,  ""),
 };
 
 #define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
@@ -1926,7 +1938,7 @@ update_view(struct view *view)
                        line[linelen - 1] = 0;
 
                if (opt_iconv != ICONV_NONE) {
-                       char *inbuf = line;
+                       ICONV_INBUF_TYPE inbuf = line;
                        size_t inlen = linelen;
 
                        char *outbuf = out_buffer;
@@ -2200,6 +2212,15 @@ view_driver(struct view *view, enum request request)
                open_view(view, request, OPEN_DEFAULT);
                break;
 
+       case REQ_VIEW_STAGE:
+               if (!VIEW(REQ_VIEW_STAGE)->lines) {
+                       report("No stage content, press %s to open the status view and choose file",
+                              get_key(REQ_VIEW_STATUS));
+                       break;
+               }
+               open_view(view, request, OPEN_DEFAULT);
+               break;
+
        case REQ_VIEW_MAIN:
        case REQ_VIEW_DIFF:
        case REQ_VIEW_LOG:
@@ -2215,7 +2236,7 @@ view_driver(struct view *view, enum request request)
 
                if ((view == VIEW(REQ_VIEW_DIFF) &&
                     view->parent == VIEW(REQ_VIEW_MAIN)) ||
-                  (view == VIEW(REQ_VIEW_DIFF) &&
+                  (view == VIEW(REQ_VIEW_STAGE) &&
                     view->parent == VIEW(REQ_VIEW_STATUS)) ||
                   (view == VIEW(REQ_VIEW_BLOB) &&
                     view->parent == VIEW(REQ_VIEW_TREE))) {
@@ -2286,7 +2307,7 @@ view_driver(struct view *view, enum request request)
                break;
 
        case REQ_SHOW_VERSION:
-               report("tig-%s (built %s)", VERSION, __DATE__);
+               report("tig-%s (built %s)", TIG_VERSION, __DATE__);
                return TRUE;
 
        case REQ_SCREEN_RESIZE:
@@ -2886,6 +2907,9 @@ struct status {
        char name[SIZEOF_STR];
 };
 
+static struct status stage_status;
+static enum line_type stage_line_type;
+
 /* Get fields from the diff line:
  * :100644 100644 06a5d6ae9eca55be2e0e585a152e6b1336f2b20e 0000000000000000000000000000000000000000 M
  */
@@ -3163,9 +3187,16 @@ status_enter(struct view *view, struct line *line)
                die("w00t");
        }
 
-       open_view(view, REQ_VIEW_DIFF, OPEN_RELOAD | OPEN_SPLIT);
-       if (view_is_displayed(VIEW(REQ_VIEW_DIFF))) {
-               string_format(VIEW(REQ_VIEW_DIFF)->ref, info, status->name);
+       open_view(view, REQ_VIEW_STAGE, OPEN_RELOAD | OPEN_SPLIT);
+       if (view_is_displayed(VIEW(REQ_VIEW_STAGE))) {
+               if (status) {
+                       stage_status = *status;
+               } else {
+                       memset(&stage_status, 0, sizeof(stage_status));
+               }
+
+               stage_line_type = line->type;
+               string_format(VIEW(REQ_VIEW_STAGE)->ref, info, stage_status.name);
        }
 
        return REQ_NONE;
@@ -3359,6 +3390,171 @@ static struct view_ops status_ops = {
 };
 
 
+static bool
+stage_diff_line(FILE *pipe, struct line *line)
+{
+       char *buf = line->data;
+       size_t bufsize = strlen(buf);
+       size_t written = 0;
+
+       while (!ferror(pipe) && written < bufsize) {
+               written += fwrite(buf + written, 1, bufsize - written, pipe);
+       }
+
+       fputc('\n', pipe);
+
+       return written == bufsize;
+}
+
+static struct line *
+stage_diff_hdr(struct view *view, struct line *line)
+{
+       int diff_hdr_dir = line->type == LINE_DIFF_CHUNK ? -1 : 1;
+       struct line *diff_hdr;
+
+       if (line->type == LINE_DIFF_CHUNK)
+               diff_hdr = line - 1;
+       else
+               diff_hdr = view->line + 1;
+
+       while (diff_hdr > view->line && diff_hdr < view->line + view->lines) {
+               if (diff_hdr->type == LINE_DIFF_HEADER)
+                       return diff_hdr;
+
+               diff_hdr += diff_hdr_dir;
+       }
+
+       return NULL;
+}
+
+static bool
+stage_update_chunk(struct view *view, struct line *line)
+{
+       char cmd[SIZEOF_STR];
+       size_t cmdsize = 0;
+       struct line *diff_hdr, *diff_chunk, *diff_end;
+       FILE *pipe;
+
+       diff_hdr = stage_diff_hdr(view, line);
+       if (!diff_hdr)
+               return FALSE;
+
+       if (opt_cdup[0] &&
+           !string_format_from(cmd, &cmdsize, "cd %s;", opt_cdup))
+               return FALSE;
+
+       if (!string_format_from(cmd, &cmdsize,
+                               "git apply --cached %s - && "
+                               "git update-index -q --unmerged --refresh 2>/dev/null",
+                               stage_line_type == LINE_STAT_STAGED ? "-R" : ""))
+               return FALSE;
+
+       pipe = popen(cmd, "w");
+       if (!pipe)
+               return FALSE;
+
+       diff_end = view->line + view->lines;
+       if (line->type != LINE_DIFF_CHUNK) {
+               diff_chunk = diff_hdr;
+
+       } else {
+               for (diff_chunk = line + 1; diff_chunk < diff_end; diff_chunk++)
+                       if (diff_chunk->type == LINE_DIFF_CHUNK ||
+                           diff_chunk->type == LINE_DIFF_HEADER)
+                               diff_end = diff_chunk;
+
+               diff_chunk = line;
+
+               while (diff_hdr->type != LINE_DIFF_CHUNK) {
+                       switch (diff_hdr->type) {
+                       case LINE_DIFF_HEADER:
+                       case LINE_DIFF_INDEX:
+                       case LINE_DIFF_ADD:
+                       case LINE_DIFF_DEL:
+                               break;
+
+                       default:
+                               diff_hdr++;
+                               continue;
+                       }
+
+                       if (!stage_diff_line(pipe, diff_hdr++)) {
+                               pclose(pipe);
+                               return FALSE;
+                       }
+               }
+       }
+
+       while (diff_chunk < diff_end && stage_diff_line(pipe, diff_chunk))
+               diff_chunk++;
+
+       pclose(pipe);
+
+       if (diff_chunk != diff_end)
+               return FALSE;
+
+       return TRUE;
+}
+
+static void
+stage_update(struct view *view, struct line *line)
+{
+       if (stage_line_type != LINE_STAT_UNTRACKED &&
+           (line->type == LINE_DIFF_CHUNK || !stage_status.status)) {
+               if (!stage_update_chunk(view, line)) {
+                       report("Failed to apply chunk");
+                       return;
+               }
+
+       } else if (!status_update_file(view, &stage_status, stage_line_type)) {
+               report("Failed to update file");
+               return;
+       }
+
+       open_view(view, REQ_VIEW_STATUS, OPEN_RELOAD);
+
+       view = VIEW(REQ_VIEW_STATUS);
+       if (view_is_displayed(view))
+               status_enter(view, &view->line[view->lineno]);
+}
+
+static enum request
+stage_request(struct view *view, enum request request, struct line *line)
+{
+       switch (request) {
+       case REQ_STATUS_UPDATE:
+               stage_update(view, line);
+               break;
+
+       case REQ_EDIT:
+               if (!stage_status.name[0])
+                       return request;
+
+               open_editor(view, stage_status.name);
+               break;
+
+       case REQ_ENTER:
+               pager_request(view, request, line);
+               break;
+
+       default:
+               return request;
+       }
+
+       return REQ_NONE;
+}
+
+static struct view_ops stage_ops = {
+       "line",
+       NULL,
+       pager_read,
+       pager_draw,
+       stage_request,
+       pager_grep,
+       pager_select,
+};
+
+
 /*
  * Revision graph
  */
@@ -4302,7 +4498,7 @@ read_repo_config_option(char *name, size_t namelen, char *value, size_t valuelen
 static int
 load_repo_config(void)
 {
-       return read_properties(popen("git repo-config --list", "r"),
+       return read_properties(popen(GIT_CONFIG " --list", "r"),
                               "=", read_repo_config_option);
 }