git-p4: After submission to p4 always synchronize from p4 again (into refs/remotes). Whether to rebase HEAD or not is still left as question to the end-user.
[gitweb.git] / fast-import.c
index f9bfcc72c87bf79fcb7a5faef01bc0d12aa15420..078079d404d8245d73ea8ef36d764f2bb311d0a9 100644 (file)
@@ -8,10 +8,11 @@ Format of STDIN stream:
         | new_tag
         | reset_branch
         | checkpoint
+        | progress
         ;
 
   new_blob ::= 'blob' lf
-       mark?
+    mark?
     file_content;
   file_content ::= data;
 
@@ -23,45 +24,55 @@ Format of STDIN stream:
     ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?
     ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)*
     file_change*
-    lf;
+    lf?;
   commit_msg ::= data;
 
-  file_change ::= file_clr | file_del | file_obm | file_inm;
+  file_change ::= file_clr
+    | file_del
+    | file_rnm
+    | file_cpy
+    | file_obm
+    | file_inm;
   file_clr ::= 'deleteall' lf;
   file_del ::= 'D' sp path_str lf;
+  file_rnm ::= 'R' sp path_str sp path_str lf;
+  file_cpy ::= 'C' sp path_str sp path_str lf;
   file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
   file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
     data;
 
   new_tag ::= 'tag' sp tag_str lf
     'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf
-       'tagger' sp name '<' email '>' when lf
+    'tagger' sp name '<' email '>' when lf
     tag_msg;
   tag_msg ::= data;
 
   reset_branch ::= 'reset' sp ref_str lf
     ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?
-    lf;
+    lf?;
 
   checkpoint ::= 'checkpoint' lf
-    lf;
+    lf?;
+
+  progress ::= 'progress' sp not_lf* lf
+    lf?;
 
      # note: the first idnum in a stream should be 1 and subsequent
      # idnums should not have gaps between values as this will cause
      # the stream parser to reserve space for the gapped values.  An
-        # idnum can be updated in the future to a new object by issuing
+     # idnum can be updated in the future to a new object by issuing
      # a new mark directive with the old idnum.
-        #
+     #
   mark ::= 'mark' sp idnum lf;
   data ::= (delimited_data | exact_data)
-    lf;
+    lf?;
 
     # note: delim may be any string but must not contain lf.
     # data_line may contain any data but must not be exactly
     # delim.
   delimited_data ::= 'data' sp '<<' delim lf
     (data_line lf)*
-       delim lf;
+    delim lf;
 
      # note: declen indicates the length of binary_data in bytes.
      # declen does not include the lf preceeding the binary data.
@@ -71,10 +82,10 @@ Format of STDIN stream:
 
      # note: quoted strings are C-style quoting supporting \c for
      # common escapes of 'c' (e..g \n, \t, \\, \") or \nnn where nnn
-        # is the signed byte value in octal.  Note that the only
+     # is the signed byte value in octal.  Note that the only
      # characters which must actually be escaped to protect the
      # stream formatting is: \, " and LF.  Otherwise these values
-        # are UTF8.
+     # are UTF8.
      #
   ref_str     ::= ref;
   sha1exp_str ::= sha1exp;
@@ -97,9 +108,9 @@ Format of STDIN stream:
   lf ::= # ASCII newline (LF) character;
 
      # note: a colon (':') must precede the numerical value assigned to
-        # an idnum.  This is to distinguish it from a ref or tag name as
+     # an idnum.  This is to distinguish it from a ref or tag name as
      # GIT does not permit ':' in ref or tag strings.
-        #
+     #
   idnum   ::= ':' bigint;
   path    ::= # GIT style file path, e.g. "a/b/c";
   ref     ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT";
@@ -108,13 +119,24 @@ Format of STDIN stream:
   hexsha1 ::= # SHA1 in hexadecimal format;
 
      # note: name and email are UTF8 strings, however name must not
-        # contain '<' or lf and email must not contain any of the
+     # contain '<' or lf and email must not contain any of the
      # following: '<', '>', lf.
-        #
+     #
   name  ::= # valid GIT author/committer name;
   email ::= # valid GIT author/committer email;
   ts    ::= # time since the epoch in seconds, ascii base10 notation;
   tz    ::= # GIT style timezone;
+
+     # note: comments may appear anywhere in the input, except
+     # within a data command.  Any form of the data command
+     # always escapes the related input from comment processing.
+     #
+     # In case it is not clear, the '#' that starts the comment
+     # must be the first character on that the line (an lf have
+     # preceeded it).
+     #
+  comment ::= '#' not_lf* lf;
+  not_lf  ::= # Any byte that is not ASCII newline (LF);
 */
 
 #include "builtin.h"
@@ -247,6 +269,13 @@ typedef enum {
        WHENSPEC_NOW,
 } whenspec_type;
 
+struct recent_command
+{
+       struct recent_command *prev;
+       struct recent_command *next;
+       char *buf;
+};
+
 /* Configured limits on output */
 static unsigned long max_depth = 10;
 static off_t max_packsize = (1LL << 32) - 1;
@@ -312,9 +341,120 @@ static struct tag *last_tag;
 /* Input stream parsing */
 static whenspec_type whenspec = WHENSPEC_RAW;
 static struct strbuf command_buf;
+static int unread_command_buf;
+static struct recent_command cmd_hist = {&cmd_hist, &cmd_hist, NULL};
+static struct recent_command *cmd_tail = &cmd_hist;
+static struct recent_command *rc_free;
+static unsigned int cmd_save = 100;
 static uintmax_t next_mark;
 static struct dbuf new_data;
 
+static void write_branch_report(FILE *rpt, struct branch *b)
+{
+       fprintf(rpt, "%s:\n", b->name);
+
+       fprintf(rpt, "  status      :");
+       if (b->active)
+               fputs(" active", rpt);
+       if (b->branch_tree.tree)
+               fputs(" loaded", rpt);
+       if (is_null_sha1(b->branch_tree.versions[1].sha1))
+               fputs(" dirty", rpt);
+       fputc('\n', rpt);
+
+       fprintf(rpt, "  tip commit  : %s\n", sha1_to_hex(b->sha1));
+       fprintf(rpt, "  old tree    : %s\n", sha1_to_hex(b->branch_tree.versions[0].sha1));
+       fprintf(rpt, "  cur tree    : %s\n", sha1_to_hex(b->branch_tree.versions[1].sha1));
+       fprintf(rpt, "  commit clock: %" PRIuMAX "\n", b->last_commit);
+
+       fputs("  last pack   : ", rpt);
+       if (b->pack_id < MAX_PACK_ID)
+               fprintf(rpt, "%u", b->pack_id);
+       fputc('\n', rpt);
+
+       fputc('\n', rpt);
+}
+
+static void write_crash_report(const char *err)
+{
+       char *loc = git_path("fast_import_crash_%d", getpid());
+       FILE *rpt = fopen(loc, "w");
+       struct branch *b;
+       unsigned long lu;
+       struct recent_command *rc;
+
+       if (!rpt) {
+               error("can't write crash report %s: %s", loc, strerror(errno));
+               return;
+       }
+
+       fprintf(stderr, "fast-import: dumping crash report to %s\n", loc);
+
+       fprintf(rpt, "fast-import crash report:\n");
+       fprintf(rpt, "    fast-import process: %d\n", getpid());
+       fprintf(rpt, "    parent process     : %d\n", getppid());
+       fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_LOCAL));
+       fputc('\n', rpt);
+
+       fputs("fatal: ", rpt);
+       fputs(err, rpt);
+       fputc('\n', rpt);
+
+       fputc('\n', rpt);
+       fputs("Most Recent Commands Before Crash\n", rpt);
+       fputs("---------------------------------\n", rpt);
+       for (rc = cmd_hist.next; rc != &cmd_hist; rc = rc->next) {
+               if (rc->next == &cmd_hist)
+                       fputs("* ", rpt);
+               else
+                       fputs("  ", rpt);
+               fputs(rc->buf, rpt);
+               fputc('\n', rpt);
+       }
+
+       fputc('\n', rpt);
+       fputs("Active Branch LRU\n", rpt);
+       fputs("-----------------\n", rpt);
+       fprintf(rpt, "    active_branches = %lu cur, %lu max\n",
+               cur_active_branches,
+               max_active_branches);
+       fputc('\n', rpt);
+       fputs("  pos  clock name\n", rpt);
+       fputs("  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n", rpt);
+       for (b = active_branches, lu = 0; b; b = b->active_next_branch)
+               fprintf(rpt, "  %2lu) %6" PRIuMAX" %s\n",
+                       ++lu, b->last_commit, b->name);
+
+       fputc('\n', rpt);
+       fputs("Inactive Branches\n", rpt);
+       fputs("-----------------\n", rpt);
+       for (lu = 0; lu < branch_table_sz; lu++) {
+               for (b = branch_table[lu]; b; b = b->table_next_branch)
+                       write_branch_report(rpt, b);
+       }
+
+       fputc('\n', rpt);
+       fputs("-------------------\n", rpt);
+       fputs("END OF CRASH REPORT\n", rpt);
+       fclose(rpt);
+}
+
+static NORETURN void die_nicely(const char *err, va_list params)
+{
+       static int zombie;
+       char message[2 * PATH_MAX];
+
+       vsnprintf(message, sizeof(message), err, params);
+       fputs("fatal: ", stderr);
+       fputs(message, stderr);
+       fputc('\n', stderr);
+
+       if (!zombie) {
+               zombie = 1;
+               write_crash_report(message);
+       }
+       exit(128);
+}
 
 static void alloc_objects(unsigned int cnt)
 {
@@ -517,8 +657,12 @@ static struct branch *new_branch(const char *name)
 
        if (b)
                die("Invalid attempt to create duplicate branch: %s", name);
-       if (check_ref_format(name))
+       switch (check_ref_format(name)) {
+       case  0: break; /* its valid */
+       case -2: break; /* valid, but too few '/', allow anyway */
+       default:
                die("Branch name doesn't conform to GIT standards: %s", name);
+       }
 
        b = pool_calloc(1, sizeof(struct branch));
        b->name = pool_strdup(name);
@@ -622,6 +766,31 @@ static void release_tree_entry(struct tree_entry *e)
        avail_tree_entry = e;
 }
 
+static struct tree_content *dup_tree_content(struct tree_content *s)
+{
+       struct tree_content *d;
+       struct tree_entry *a, *b;
+       unsigned int i;
+
+       if (!s)
+               return NULL;
+       d = new_tree_content(s->entry_count);
+       for (i = 0; i < s->entry_count; i++) {
+               a = s->entries[i];
+               b = new_tree_entry();
+               memcpy(b, a, sizeof(*a));
+               if (a->tree && is_null_sha1(b->versions[1].sha1))
+                       b->tree = dup_tree_content(a->tree);
+               else
+                       b->tree = NULL;
+               d->entries[i] = b;
+       }
+       d->entry_count = s->entry_count;
+       d->delta_depth = s->delta_depth;
+
+       return d;
+}
+
 static void start_packfile(void)
 {
        static char tmpfile[PATH_MAX];
@@ -631,9 +800,7 @@ static void start_packfile(void)
 
        snprintf(tmpfile, sizeof(tmpfile),
                "%s/tmp_pack_XXXXXX", get_object_directory());
-       pack_fd = mkstemp(tmpfile);
-       if (pack_fd < 0)
-               die("Can't create %s: %s", tmpfile, strerror(errno));
+       pack_fd = xmkstemp(tmpfile);
        p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
        strcpy(p->pack_name, tmpfile);
        p->pack_fd = pack_fd;
@@ -695,9 +862,7 @@ static char *create_index(void)
 
        snprintf(tmpfile, sizeof(tmpfile),
                "%s/tmp_idx_XXXXXX", get_object_directory());
-       idx_fd = mkstemp(tmpfile);
-       if (idx_fd < 0)
-               die("Can't create %s: %s", tmpfile, strerror(errno));
+       idx_fd = xmkstemp(tmpfile);
        f = sha1fd(idx_fd, tmpfile);
        sha1write(f, array, 256 * sizeof(int));
        SHA1_Init(&ctx);
@@ -1154,7 +1319,8 @@ static int tree_content_set(
        struct tree_entry *root,
        const char *p,
        const unsigned char *sha1,
-       const uint16_t mode)
+       const uint16_t mode,
+       struct tree_content *subtree)
 {
        struct tree_content *t = root->tree;
        const char *slash1;
@@ -1168,20 +1334,22 @@ static int tree_content_set(
                n = strlen(p);
        if (!n)
                die("Empty path component found in input");
+       if (!slash1 && !S_ISDIR(mode) && subtree)
+               die("Non-directories cannot have subtrees");
 
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
                if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
                        if (!slash1) {
-                               if (e->versions[1].mode == mode
+                               if (!S_ISDIR(mode)
+                                               && e->versions[1].mode == mode
                                                && !hashcmp(e->versions[1].sha1, sha1))
                                        return 0;
                                e->versions[1].mode = mode;
                                hashcpy(e->versions[1].sha1, sha1);
-                               if (e->tree) {
+                               if (e->tree)
                                        release_tree_content_recursive(e->tree);
-                                       e->tree = NULL;
-                               }
+                               e->tree = subtree;
                                hashclr(root->versions[1].sha1);
                                return 1;
                        }
@@ -1191,7 +1359,7 @@ static int tree_content_set(
                        }
                        if (!e->tree)
                                load_tree(e);
-                       if (tree_content_set(e, slash1 + 1, sha1, mode)) {
+                       if (tree_content_set(e, slash1 + 1, sha1, mode, subtree)) {
                                hashclr(root->versions[1].sha1);
                                return 1;
                        }
@@ -1209,9 +1377,9 @@ static int tree_content_set(
        if (slash1) {
                e->tree = new_tree_content(8);
                e->versions[1].mode = S_IFDIR;
-               tree_content_set(e, slash1 + 1, sha1, mode);
+               tree_content_set(e, slash1 + 1, sha1, mode, subtree);
        } else {
-               e->tree = NULL;
+               e->tree = subtree;
                e->versions[1].mode = mode;
                hashcpy(e->versions[1].sha1, sha1);
        }
@@ -1219,7 +1387,10 @@ static int tree_content_set(
        return 1;
 }
 
-static int tree_content_remove(struct tree_entry *root, const char *p)
+static int tree_content_remove(
+       struct tree_entry *root,
+       const char *p,
+       struct tree_entry *backup_leaf)
 {
        struct tree_content *t = root->tree;
        const char *slash1;
@@ -1239,13 +1410,14 @@ static int tree_content_remove(struct tree_entry *root, const char *p)
                                goto del_entry;
                        if (!e->tree)
                                load_tree(e);
-                       if (tree_content_remove(e, slash1 + 1)) {
+                       if (tree_content_remove(e, slash1 + 1, backup_leaf)) {
                                for (n = 0; n < e->tree->entry_count; n++) {
                                        if (e->tree->entries[n]->versions[1].mode) {
                                                hashclr(root->versions[1].sha1);
                                                return 1;
                                        }
                                }
+                               backup_leaf = NULL;
                                goto del_entry;
                        }
                        return 0;
@@ -1254,16 +1426,54 @@ static int tree_content_remove(struct tree_entry *root, const char *p)
        return 0;
 
 del_entry:
-       if (e->tree) {
+       if (backup_leaf)
+               memcpy(backup_leaf, e, sizeof(*backup_leaf));
+       else if (e->tree)
                release_tree_content_recursive(e->tree);
-               e->tree = NULL;
-       }
+       e->tree = NULL;
        e->versions[1].mode = 0;
        hashclr(e->versions[1].sha1);
        hashclr(root->versions[1].sha1);
        return 1;
 }
 
+static int tree_content_get(
+       struct tree_entry *root,
+       const char *p,
+       struct tree_entry *leaf)
+{
+       struct tree_content *t = root->tree;
+       const char *slash1;
+       unsigned int i, n;
+       struct tree_entry *e;
+
+       slash1 = strchr(p, '/');
+       if (slash1)
+               n = slash1 - p;
+       else
+               n = strlen(p);
+
+       for (i = 0; i < t->entry_count; i++) {
+               e = t->entries[i];
+               if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+                       if (!slash1) {
+                               memcpy(leaf, e, sizeof(*leaf));
+                               if (e->tree && is_null_sha1(e->versions[1].sha1))
+                                       leaf->tree = dup_tree_content(e->tree);
+                               else
+                                       leaf->tree = NULL;
+                               return 1;
+                       }
+                       if (!S_ISDIR(e->versions[1].mode))
+                               return 0;
+                       if (!e->tree)
+                               load_tree(e);
+                       return tree_content_get(e, slash1 + 1, leaf);
+               }
+       }
+       return 0;
+}
+
 static int update_branch(struct branch *b)
 {
        static const char *msg = "fast-import";
@@ -1377,7 +1587,43 @@ static void dump_marks(void)
 
 static void read_next_command(void)
 {
-       read_line(&command_buf, stdin, '\n');
+       do {
+               if (unread_command_buf) {
+                       unread_command_buf = 0;
+                       if (command_buf.eof)
+                               return;
+               } else {
+                       struct recent_command *rc;
+
+                       command_buf.buf = NULL;
+                       read_line(&command_buf, stdin, '\n');
+                       if (command_buf.eof)
+                               return;
+
+                       rc = rc_free;
+                       if (rc)
+                               rc_free = rc->next;
+                       else {
+                               rc = cmd_hist.next;
+                               cmd_hist.next = rc->next;
+                               cmd_hist.next->prev = &cmd_hist;
+                               free(rc->buf);
+                       }
+
+                       rc->buf = command_buf.buf;
+                       rc->prev = cmd_tail;
+                       rc->next = cmd_hist.prev;
+                       rc->prev->next = rc;
+                       cmd_tail = rc;
+               }
+       } while (command_buf.buf[0] == '#');
+}
+
+static void skip_optional_lf(void)
+{
+       int term_char = fgetc(stdin);
+       if (term_char != '\n' && term_char != EOF)
+               ungetc(term_char, stdin);
 }
 
 static void cmd_mark(void)
@@ -1403,19 +1649,15 @@ static void *cmd_data (size_t *size)
                size_t sz = 8192, term_len = command_buf.len - 5 - 2;
                length = 0;
                buffer = xmalloc(sz);
+               command_buf.buf = NULL;
                for (;;) {
-                       read_next_command();
+                       read_line(&command_buf, stdin, '\n');
                        if (command_buf.eof)
                                die("EOF in data (terminator '%s' not found)", term);
                        if (term_len == command_buf.len
                                && !strcmp(term, command_buf.buf))
                                break;
-                       if (sz < (length + command_buf.len)) {
-                               sz = sz * 3 / 2 + 16;
-                               if (sz < (length + command_buf.len))
-                                       sz = length + command_buf.len;
-                               buffer = xrealloc(buffer, sz);
-                       }
+                       ALLOC_GROW(buffer, length + command_buf.len, sz);
                        memcpy(buffer + length,
                                command_buf.buf,
                                command_buf.len - 1);
@@ -1437,9 +1679,7 @@ static void *cmd_data (size_t *size)
                }
        }
 
-       if (fgetc(stdin) != '\n')
-               die("An lf did not trail the binary data as expected.");
-
+       skip_optional_lf();
        *size = length;
        return buffer;
 }
@@ -1629,7 +1869,7 @@ static void file_change_m(struct branch *b)
                            typename(type), command_buf.buf);
        }
 
-       tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode);
+       tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode, NULL);
        free(p_uq);
 }
 
@@ -1645,10 +1885,61 @@ static void file_change_d(struct branch *b)
                        die("Garbage after path in: %s", command_buf.buf);
                p = p_uq;
        }
-       tree_content_remove(&b->branch_tree, p);
+       tree_content_remove(&b->branch_tree, p, NULL);
        free(p_uq);
 }
 
+static void file_change_cr(struct branch *b, int rename)
+{
+       const char *s, *d;
+       char *s_uq, *d_uq;
+       const char *endp;
+       struct tree_entry leaf;
+
+       s = command_buf.buf + 2;
+       s_uq = unquote_c_style(s, &endp);
+       if (s_uq) {
+               if (*endp != ' ')
+                       die("Missing space after source: %s", command_buf.buf);
+       }
+       else {
+               endp = strchr(s, ' ');
+               if (!endp)
+                       die("Missing space after source: %s", command_buf.buf);
+               s_uq = xmalloc(endp - s + 1);
+               memcpy(s_uq, s, endp - s);
+               s_uq[endp - s] = 0;
+       }
+       s = s_uq;
+
+       endp++;
+       if (!*endp)
+               die("Missing dest: %s", command_buf.buf);
+
+       d = endp;
+       d_uq = unquote_c_style(d, &endp);
+       if (d_uq) {
+               if (*endp)
+                       die("Garbage after dest in: %s", command_buf.buf);
+               d = d_uq;
+       }
+
+       memset(&leaf, 0, sizeof(leaf));
+       if (rename)
+               tree_content_remove(&b->branch_tree, s, &leaf);
+       else
+               tree_content_get(&b->branch_tree, s, &leaf);
+       if (!leaf.versions[1].mode)
+               die("Path %s not in branch", s);
+       tree_content_set(&b->branch_tree, d,
+               leaf.versions[1].sha1,
+               leaf.versions[1].mode,
+               leaf.tree);
+
+       free(s_uq);
+       free(d_uq);
+}
+
 static void file_change_deleteall(struct branch *b)
 {
        release_tree_content_recursive(b->branch_tree.tree);
@@ -1684,13 +1975,13 @@ static void cmd_from_existing(struct branch *b)
        }
 }
 
-static void cmd_from(struct branch *b)
+static int cmd_from(struct branch *b)
 {
        const char *from;
        struct branch *s;
 
        if (prefixcmp(command_buf.buf, "from "))
-               return;
+               return 0;
 
        if (b->branch_tree.tree) {
                release_tree_content_recursive(b->branch_tree.tree);
@@ -1725,6 +2016,7 @@ static void cmd_from(struct branch *b)
                die("Invalid ref name or SHA1 expression: %s", from);
 
        read_next_command();
+       return 1;
 }
 
 static struct hash_list *cmd_merge(unsigned int *count)
@@ -1809,17 +2101,21 @@ static void cmd_new_commit(void)
        }
 
        /* file_change* */
-       for (;;) {
-               if (1 == command_buf.len)
-                       break;
-               else if (!prefixcmp(command_buf.buf, "M "))
+       while (!command_buf.eof && command_buf.len > 1) {
+               if (!prefixcmp(command_buf.buf, "M "))
                        file_change_m(b);
                else if (!prefixcmp(command_buf.buf, "D "))
                        file_change_d(b);
+               else if (!prefixcmp(command_buf.buf, "R "))
+                       file_change_cr(b, 1);
+               else if (!prefixcmp(command_buf.buf, "C "))
+                       file_change_cr(b, 0);
                else if (!strcmp("deleteall", command_buf.buf))
                        file_change_deleteall(b);
-               else
-                       die("Unsupported file_change: %s", command_buf.buf);
+               else {
+                       unread_command_buf = 1;
+                       break;
+               }
                read_next_command();
        }
 
@@ -1960,7 +2256,8 @@ static void cmd_reset_branch(void)
        else
                b = new_branch(sp);
        read_next_command();
-       cmd_from(b);
+       if (!cmd_from(b) && command_buf.len > 1)
+               unread_command_buf = 1;
 }
 
 static void cmd_checkpoint(void)
@@ -1971,7 +2268,15 @@ static void cmd_checkpoint(void)
                dump_tags();
                dump_marks();
        }
-       read_next_command();
+       skip_optional_lf();
+}
+
+static void cmd_progress(void)
+{
+       fwrite(command_buf.buf, 1, command_buf.len - 1, stdout);
+       fputc('\n', stdout);
+       fflush(stdout);
+       skip_optional_lf();
 }
 
 static void import_marks(const char *input_file)
@@ -2014,7 +2319,7 @@ static const char fast_import_usage[] =
 
 int main(int argc, const char **argv)
 {
-       int i, show_stats = 1;
+       unsigned int i, show_stats = 1;
 
        git_config(git_default_config);
        alloc_objects(object_entry_alloc);
@@ -2068,8 +2373,14 @@ int main(int argc, const char **argv)
        if (i != argc)
                usage(fast_import_usage);
 
+       rc_free = pool_alloc(cmd_save * sizeof(*rc_free));
+       for (i = 0; i < (cmd_save - 1); i++)
+               rc_free[i].next = &rc_free[i + 1];
+       rc_free[cmd_save - 1].next = NULL;
+
        prepare_packed_git();
        start_packfile();
+       set_die_routine(die_nicely);
        for (;;) {
                read_next_command();
                if (command_buf.eof)
@@ -2084,6 +2395,8 @@ int main(int argc, const char **argv)
                        cmd_reset_branch();
                else if (!strcmp("checkpoint", command_buf.buf))
                        cmd_checkpoint();
+               else if (!prefixcmp(command_buf.buf, "progress "))
+                       cmd_progress();
                else
                        die("Unsupported command: %s", command_buf.buf);
        }