Code

IO API: refactor the run request command formatter
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index d2bc2dd46555008a03b6aa903fea78ea304ecfbf..3fce6f086685395490beacd03f7afb968c5c7670 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -166,6 +166,15 @@ struct ref {
 
 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;
@@ -332,6 +341,147 @@ sq_quote(char buf[SIZEOF_STR], size_t bufsize, const char *src)
        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
@@ -742,7 +892,6 @@ struct line {
 struct keybinding {
        int alias;
        enum request request;
-       struct keybinding *next;
 };
 
 static struct keybinding default_keybindings[] = {
@@ -841,21 +990,23 @@ static struct int_map keymap_table[] = {
 #define set_keymap(map, name) \
        set_from_int_map(keymap_table, ARRAY_SIZE(keymap_table), map, name, strlen(name))
 
-static struct keybinding *keybindings[ARRAY_SIZE(keymap_table)];
+struct keybinding_table {
+       struct keybinding *data;
+       size_t size;
+};
+
+static struct keybinding_table keybindings[ARRAY_SIZE(keymap_table)];
 
 static void
 add_keybinding(enum keymap keymap, enum request request, int key)
 {
-       struct keybinding *keybinding;
+       struct keybinding_table *table = &keybindings[keymap];
 
-       keybinding = calloc(1, sizeof(*keybinding));
-       if (!keybinding)
+       table->data = realloc(table->data, (table->size + 1) * sizeof(*table->data));
+       if (!table->data)
                die("Failed to allocate keybinding");
-
-       keybinding->alias = key;
-       keybinding->request = request;
-       keybinding->next = keybindings[keymap];
-       keybindings[keymap] = keybinding;
+       table->data[table->size].alias = key;
+       table->data[table->size++].request = request;
 }
 
 /* Looks for a key binding first in the given map, then in the generic map, and
@@ -863,16 +1014,15 @@ add_keybinding(enum keymap keymap, enum request request, int key)
 static enum request
 get_keybinding(enum keymap keymap, int key)
 {
-       struct keybinding *kbd;
-       int i;
+       size_t i;
 
-       for (kbd = keybindings[keymap]; kbd; kbd = kbd->next)
-               if (kbd->alias == key)
-                       return kbd->request;
+       for (i = 0; i < keybindings[keymap].size; i++)
+               if (keybindings[keymap].data[i].alias == key)
+                       return keybindings[keymap].data[i].request;
 
-       for (kbd = keybindings[KEYMAP_GENERIC]; kbd; kbd = kbd->next)
-               if (kbd->alias == key)
-                       return kbd->request;
+       for (i = 0; i < keybindings[KEYMAP_GENERIC].size; i++)
+               if (keybindings[KEYMAP_GENERIC].data[i].alias == key)
+                       return keybindings[KEYMAP_GENERIC].data[i].request;
 
        for (i = 0; i < ARRAY_SIZE(default_keybindings); i++)
                if (default_keybindings[i].alias == key)
@@ -982,7 +1132,7 @@ get_key(enum request request)
 struct run_request {
        enum keymap keymap;
        int key;
-       char cmd[SIZEOF_STR];
+       const char *argv[SIZEOF_ARG];
 };
 
 static struct run_request *run_request;
@@ -992,24 +1142,24 @@ static enum 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 *
@@ -1023,20 +1173,23 @@ get_run_request(enum request 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);
        }
@@ -1268,21 +1421,11 @@ static int
 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"))
@@ -1419,7 +1562,6 @@ struct view {
        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. */
 
@@ -1452,7 +1594,8 @@ struct view {
        unsigned long col;      /* Column when drawing. */
 
        /* Loading */
-       FILE *pipe;
+       struct io io;
+       struct io *pipe;
        time_t start_time;
 };
 
@@ -2218,6 +2361,103 @@ reset_view(struct view *view)
        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)
 {
@@ -2227,26 +2467,41 @@ 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;
 }
 
+static void
+setup_update(struct view *view, const char *vid)
+{
+       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;
@@ -2257,14 +2512,14 @@ begin_update(struct view *view, bool refresh)
                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
@@ -2274,22 +2529,7 @@ begin_update(struct view *view, bool refresh)
                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;
-
-       set_nonblocking_input(TRUE);
-       reset_view(view);
-       string_copy_rev(view->vid, view->id);
-
-       view->start_time = time(NULL);
+       setup_update(view, view->id);
 
        return TRUE;
 }
@@ -2328,7 +2568,6 @@ realloc_lines(struct view *view, size_t line_size)
 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
@@ -2348,7 +2587,7 @@ update_view(struct view *view)
        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)
@@ -2391,11 +2630,11 @@ update_view(struct view *view)
                }
        }
 
-       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);
        }
@@ -2624,49 +2863,14 @@ open_run_request(enum request request)
 {
        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);
 }
 
 /*
@@ -3171,6 +3375,9 @@ help_open(struct view *view)
        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;
@@ -3179,9 +3386,13 @@ help_open(struct view *view)
                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);
@@ -3509,7 +3720,6 @@ struct blame_commit {
 
 struct blame {
        struct blame_commit *commit;
-       unsigned int header:1;
        char text[1];
 };
 
@@ -3528,29 +3738,15 @@ blame_open(struct view *view)
        if (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref))
                return FALSE;
 
-       if (*opt_ref) {
-               if (!string_format(view->cmd, BLAME_CAT_FILE_CMD, ref, path))
-                       return FALSE;
-       } else {
-               view->pipe = fopen(opt_file, "r");
-               if (!view->pipe &&
-                   !string_format(view->cmd, BLAME_CAT_FILE_CMD, "HEAD", path))
+       if (*opt_ref || !init_io_fd(&view->io, fopen(opt_file, "r"))) {
+               const char *id = *opt_ref ? ref : "HEAD";
+
+               if (!run_io_format(&view->io, BLAME_CAT_FILE_CMD, id, path))
                        return FALSE;
        }
 
-       if (!view->pipe)
-               view->pipe = popen(view->cmd, "r");
-       if (!view->pipe)
-               return FALSE;
-
-       if (!string_format(view->cmd, BLAME_INCREMENTAL_CMD, ref, path))
-               return FALSE;
-
-       reset_view(view);
+       setup_update(view, opt_file);
        string_format(view->ref, "%s ...", opt_file);
-       string_copy_rev(view->vid, opt_file);
-       set_nonblocking_input(TRUE);
-       view->start_time = time(NULL);
 
        return TRUE;
 }
@@ -3622,7 +3818,6 @@ parse_blame_commit(struct view *view, const char *text, int *blamed)
 
                blame = line->data;
                blame->commit = commit;
-               blame->header = !group;
                line->dirty = 1;
        }
 
@@ -3630,23 +3825,27 @@ parse_blame_commit(struct view *view, const char *text, int *blamed)
 }
 
 static bool
-blame_read_file(struct view *view, const char *line)
+blame_read_file(struct view *view, const char *line, bool *read_file)
 {
        if (!line) {
-               FILE *pipe = NULL;
+               char ref[SIZEOF_STR] = "";
+               char path[SIZEOF_STR];
+               struct io io = {};
 
-               if (view->lines > 0)
-                       pipe = popen(view->cmd, "r");
-               else if (!view->parent)
+               if (view->lines == 0 && !view->parent)
                        die("No blame exist for %s", view->vid);
-               view->cmd[0] = 0;
-               if (!pipe) {
+
+               if (view->lines == 0 ||
+                   sq_quote(path, 0, opt_file) >= sizeof(path) ||
+                   (*opt_ref && sq_quote(ref, 0, opt_ref) >= sizeof(ref)) ||
+                   !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;
 
        } else {
@@ -3678,14 +3877,16 @@ blame_read(struct view *view, char *line)
        static struct blame_commit *commit = NULL;
        static int blamed = 0;
        static time_t author_time;
+       static bool read_file = TRUE;
 
-       if (*view->cmd)
-               return blame_read_file(view, line);
+       if (read_file)
+               return blame_read_file(view, line, &read_file);
 
        if (!line) {
                /* Reset all! */
                commit = NULL;
                blamed = 0;
+               read_file = TRUE;
                string_format(view->ref, "%s", view->vid);
                if (view_is_displayed(view)) {
                        update_view_title(view);
@@ -3767,12 +3968,25 @@ blame_request(struct view *view, enum request request, struct line *line)
        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];