Code

Merge branch 'jn/maint-fast-import-empty-ls' into maint
authorJunio C Hamano <gitster@pobox.com>
Mon, 26 Mar 2012 19:10:25 +0000 (12:10 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 26 Mar 2012 19:10:25 +0000 (12:10 -0700)
* jn/maint-fast-import-empty-ls:
  fast-import: don't allow 'ls' of path with empty components
  fast-import: leakfix for 'ls' of dirty trees

1  2 
fast-import.c
t/t9300-fast-import.sh

diff --combined fast-import.c
index c1486cabbaa5f736f92994587fafd1f3afdf43af,47f61f3cba7b7a4cde8ae116cbce38116562bda6..a85275dc682d2bb8068f003b8281c3933488f010
@@@ -170,12 -170,8 +170,12 @@@ Format of STDIN stream
  #define DEPTH_BITS 13
  #define MAX_DEPTH ((1<<DEPTH_BITS)-1)
  
 -struct object_entry
 -{
 +/*
 + * We abuse the setuid bit on directories to mean "do not delta".
 + */
 +#define NO_DELTA S_ISUID
 +
 +struct object_entry {
        struct pack_idx_entry idx;
        struct object_entry *next;
        uint32_t type : TYPE_BITS,
                depth : DEPTH_BITS;
  };
  
 -struct object_entry_pool
 -{
 +struct object_entry_pool {
        struct object_entry_pool *next_pool;
        struct object_entry *next_free;
        struct object_entry *end;
        struct object_entry entries[FLEX_ARRAY]; /* more */
  };
  
 -struct mark_set
 -{
 +struct mark_set {
        union {
                struct object_entry *marked[1024];
                struct mark_set *sets[1024];
        unsigned int shift;
  };
  
 -struct last_object
 -{
 +struct last_object {
        struct strbuf data;
        off_t offset;
        unsigned int depth;
        unsigned no_swap : 1;
  };
  
 -struct mem_pool
 -{
 +struct mem_pool {
        struct mem_pool *next_pool;
        char *next_free;
        char *end;
        uintmax_t space[FLEX_ARRAY]; /* more */
  };
  
 -struct atom_str
 -{
 +struct atom_str {
        struct atom_str *next_atom;
        unsigned short str_len;
        char str_dat[FLEX_ARRAY]; /* more */
  };
  
  struct tree_content;
 -struct tree_entry
 -{
 +struct tree_entry {
        struct tree_content *tree;
        struct atom_str *name;
 -      struct tree_entry_ms
 -      {
 +      struct tree_entry_ms {
                uint16_t mode;
                unsigned char sha1[20];
        } versions[2];
  };
  
 -struct tree_content
 -{
 +struct tree_content {
        unsigned int entry_capacity; /* must match avail_tree_content */
        unsigned int entry_count;
        unsigned int delta_depth;
        struct tree_entry *entries[FLEX_ARRAY]; /* more */
  };
  
 -struct avail_tree_content
 -{
 +struct avail_tree_content {
        unsigned int entry_capacity; /* must match tree_content */
        struct avail_tree_content *next_avail;
  };
  
 -struct branch
 -{
 +struct branch {
        struct branch *table_next_branch;
        struct branch *active_next_branch;
        const char *name;
        unsigned char sha1[20];
  };
  
 -struct tag
 -{
 +struct tag {
        struct tag *next_tag;
        const char *name;
        unsigned int pack_id;
        unsigned char sha1[20];
  };
  
 -struct hash_list
 -{
 +struct hash_list {
        struct hash_list *next;
        unsigned char sha1[20];
  };
@@@ -270,7 -278,8 +270,7 @@@ typedef enum 
        WHENSPEC_NOW
  } whenspec_type;
  
 -struct recent_command
 -{
 +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;
 -static uintmax_t big_file_threshold = 512 * 1024 * 1024;
  static int force_update;
  static int pack_compression_level = Z_DEFAULT_COMPRESSION;
  static int pack_compression_seen;
@@@ -289,7 -299,6 +289,7 @@@ static uintmax_t marks_set_count
  static uintmax_t object_count_by_type[1 << TYPE_BITS];
  static uintmax_t duplicate_count_by_type[1 << TYPE_BITS];
  static uintmax_t delta_count_by_type[1 << TYPE_BITS];
 +static uintmax_t delta_count_attempts_by_type[1 << TYPE_BITS];
  static unsigned long object_count;
  static unsigned long branch_count;
  static unsigned long branch_load_count;
@@@ -310,7 -319,6 +310,7 @@@ static unsigned int atom_cnt
  static struct atom_str **atom_table;
  
  /* The .pack file being generated */
 +static struct pack_idx_option pack_idx_opts;
  static unsigned int pack_id;
  static struct sha1file *pack_file;
  static struct packed_git *pack_data;
@@@ -361,7 -369,6 +361,7 @@@ static unsigned int cmd_save = 100
  static uintmax_t next_mark;
  static struct strbuf new_data = STRBUF_INIT;
  static int seen_data_command;
 +static int require_explicit_termination;
  
  /* Signal handling */
  static volatile sig_atomic_t checkpoint_requested;
@@@ -722,8 -729,13 +722,8 @@@ static struct branch *new_branch(const 
  
        if (b)
                die("Invalid attempt to create duplicate branch: %s", name);
 -      switch (check_ref_format(name)) {
 -      case 0: break; /* its valid */
 -      case CHECK_REF_FORMAT_ONELEVEL:
 -              break; /* valid, but too few '/', allow anyway */
 -      default:
 +      if (check_refname_format(name, REFNAME_ALLOW_ONELEVEL))
                die("Branch name doesn't conform to GIT standards: %s", name);
 -      }
  
        b = pool_calloc(1, sizeof(struct branch));
        b->name = pool_strdup(name);
@@@ -855,17 -867,16 +855,17 @@@ static struct tree_content *dup_tree_co
  
  static void start_packfile(void)
  {
 -      static char tmpfile[PATH_MAX];
 +      static char tmp_file[PATH_MAX];
        struct packed_git *p;
        struct pack_header hdr;
        int pack_fd;
  
 -      pack_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
 +      pack_fd = odb_mkstemp(tmp_file, sizeof(tmp_file),
                              "pack/tmp_pack_XXXXXX");
 -      p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
 -      strcpy(p->pack_name, tmpfile);
 +      p = xcalloc(1, sizeof(*p) + strlen(tmp_file) + 2);
 +      strcpy(p->pack_name, tmp_file);
        p->pack_fd = pack_fd;
 +      p->do_not_close = 1;
        pack_file = sha1fd(pack_fd, p->pack_name);
  
        hdr.hdr_signature = htonl(PACK_SIGNATURE);
@@@ -899,7 -910,7 +899,7 @@@ static const char *create_index(void
        if (c != last)
                die("internal consistency error creating the index");
  
 -      tmpfile = write_idx_file(NULL, idx, object_count, pack_data->sha1);
 +      tmpfile = write_idx_file(NULL, idx, object_count, &pack_idx_opts, pack_data->sha1);
        free(idx);
        return tmpfile;
  }
@@@ -1020,7 -1031,7 +1020,7 @@@ static int store_object
        unsigned char sha1[20];
        unsigned long hdrlen, deltalen;
        git_SHA_CTX c;
 -      z_stream s;
 +      git_zstream s;
  
        hdrlen = sprintf((char *)hdr,"%s %lu", typename(type),
                (unsigned long)dat->len) + 1;
        }
  
        if (last && last->data.buf && last->depth < max_depth && dat->len > 20) {
 +              delta_count_attempts_by_type[type]++;
                delta = diff_delta(last->data.buf, last->data.len,
                        dat->buf, dat->len,
                        &deltalen, dat->len - 20);
                delta = NULL;
  
        memset(&s, 0, sizeof(s));
 -      deflateInit(&s, pack_compression_level);
 +      git_deflate_init(&s, pack_compression_level);
        if (delta) {
                s.next_in = delta;
                s.avail_in = deltalen;
                s.next_in = (void *)dat->buf;
                s.avail_in = dat->len;
        }
 -      s.avail_out = deflateBound(&s, s.avail_in);
 +      s.avail_out = git_deflate_bound(&s, s.avail_in);
        s.next_out = out = xmalloc(s.avail_out);
 -      while (deflate(&s, Z_FINISH) == Z_OK)
 -              /* nothing */;
 -      deflateEnd(&s);
 +      while (git_deflate(&s, Z_FINISH) == Z_OK)
 +              ; /* nothing */
 +      git_deflate_end(&s);
  
        /* Determine if we should auto-checkpoint. */
        if ((max_packsize && (pack_size + 60 + s.total_out) > max_packsize)
                        delta = NULL;
  
                        memset(&s, 0, sizeof(s));
 -                      deflateInit(&s, pack_compression_level);
 +                      git_deflate_init(&s, pack_compression_level);
                        s.next_in = (void *)dat->buf;
                        s.avail_in = dat->len;
 -                      s.avail_out = deflateBound(&s, s.avail_in);
 +                      s.avail_out = git_deflate_bound(&s, s.avail_in);
                        s.next_out = out = xrealloc(out, s.avail_out);
 -                      while (deflate(&s, Z_FINISH) == Z_OK)
 -                              /* nothing */;
 -                      deflateEnd(&s);
 +                      while (git_deflate(&s, Z_FINISH) == Z_OK)
 +                              ; /* nothing */
 +                      git_deflate_end(&s);
                }
        }
  
        return 0;
  }
  
 -static void truncate_pack(off_t to, git_SHA_CTX *ctx)
 +static void truncate_pack(struct sha1file_checkpoint *checkpoint)
  {
 -      if (ftruncate(pack_data->pack_fd, to)
 -       || lseek(pack_data->pack_fd, to, SEEK_SET) != to)
 +      if (sha1file_truncate(pack_file, checkpoint))
                die_errno("cannot truncate pack to skip duplicate");
 -      pack_size = to;
 -
 -      /* yes this is a layering violation */
 -      pack_file->total = to;
 -      pack_file->offset = 0;
 -      pack_file->ctx = *ctx;
 +      pack_size = checkpoint->offset;
  }
  
  static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
        unsigned long hdrlen;
        off_t offset;
        git_SHA_CTX c;
 -      git_SHA_CTX pack_file_ctx;
 -      z_stream s;
 +      git_zstream s;
 +      struct sha1file_checkpoint checkpoint;
        int status = Z_OK;
  
        /* Determine if we should auto-checkpoint. */
                || (pack_size + 60 + len) < pack_size)
                cycle_packfile();
  
 -      offset = pack_size;
 -
 -      /* preserve the pack_file SHA1 ctx in case we have to truncate later */
 -      sha1flush(pack_file);
 -      pack_file_ctx = pack_file->ctx;
 +      sha1file_checkpoint(pack_file, &checkpoint);
 +      offset = checkpoint.offset;
  
        hdrlen = snprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1;
        if (out_sz <= hdrlen)
        crc32_begin(pack_file);
  
        memset(&s, 0, sizeof(s));
 -      deflateInit(&s, pack_compression_level);
 +      git_deflate_init(&s, pack_compression_level);
  
        hdrlen = encode_in_pack_object_header(OBJ_BLOB, len, out_buf);
        if (out_sz <= hdrlen)
                        len -= n;
                }
  
 -              status = deflate(&s, len ? 0 : Z_FINISH);
 +              status = git_deflate(&s, len ? 0 : Z_FINISH);
  
                if (!s.avail_out || status == Z_STREAM_END) {
                        size_t n = s.next_out - out_buf;
                        die("unexpected deflate failure: %d", status);
                }
        }
 -      deflateEnd(&s);
 +      git_deflate_end(&s);
        git_SHA1_Final(sha1, &c);
  
        if (sha1out)
  
        if (e->idx.offset) {
                duplicate_count_by_type[OBJ_BLOB]++;
 -              truncate_pack(offset, &pack_file_ctx);
 +              truncate_pack(&checkpoint);
  
        } else if (find_sha1_pack(sha1, packed_git)) {
                e->type = OBJ_BLOB;
                e->pack_id = MAX_PACK_ID;
                e->idx.offset = 1; /* just not zero! */
                duplicate_count_by_type[OBJ_BLOB]++;
 -              truncate_pack(offset, &pack_file_ctx);
 +              truncate_pack(&checkpoint);
  
        } else {
                e->depth = 0;
@@@ -1409,9 -1428,8 +1409,9 @@@ static void mktree(struct tree_content 
                struct tree_entry *e = t->entries[i];
                if (!e->versions[v].mode)
                        continue;
 -              strbuf_addf(b, "%o %s%c", (unsigned int)e->versions[v].mode,
 -                                      e->name->str_dat, '\0');
 +              strbuf_addf(b, "%o %s%c",
 +                      (unsigned int)(e->versions[v].mode & ~NO_DELTA),
 +                      e->name->str_dat, '\0');
                strbuf_add(b, e->versions[v].sha1, 20);
        }
  }
@@@ -1421,7 -1439,7 +1421,7 @@@ static void store_tree(struct tree_entr
        struct tree_content *t = root->tree;
        unsigned int i, j, del;
        struct last_object lo = { STRBUF_INIT, 0, 0, /* no_swap */ 1 };
 -      struct object_entry *le;
 +      struct object_entry *le = NULL;
  
        if (!is_null_sha1(root->versions[1].sha1))
                return;
                        store_tree(t->entries[i]);
        }
  
 -      le = find_object(root->versions[0].sha1);
 +      if (!(root->versions[0].mode & NO_DELTA))
 +              le = find_object(root->versions[0].sha1);
        if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) {
                mktree(t, 0, &old_tree);
                lo.data = old_tree;
@@@ -1466,7 -1483,6 +1466,7 @@@ static void tree_content_replace
  {
        if (!S_ISDIR(mode))
                die("Root cannot be a non-directory");
 +      hashclr(root->versions[0].sha1);
        hashcpy(root->versions[1].sha1, sha1);
        if (root->tree)
                release_tree_content_recursive(root->tree);
@@@ -1511,23 -1527,6 +1511,23 @@@ static int tree_content_set
                                if (e->tree)
                                        release_tree_content_recursive(e->tree);
                                e->tree = subtree;
 +
 +                              /*
 +                               * We need to leave e->versions[0].sha1 alone
 +                               * to avoid modifying the preimage tree used
 +                               * when writing out the parent directory.
 +                               * But after replacing the subdir with a
 +                               * completely different one, it's not a good
 +                               * delta base any more, and besides, we've
 +                               * thrown away the tree entries needed to
 +                               * make a delta against it.
 +                               *
 +                               * So let's just explicitly disable deltas
 +                               * for the subtree.
 +                               */
 +                              if (S_ISDIR(e->versions[0].mode))
 +                                      e->versions[0].mode |= NO_DELTA;
 +
                                hashclr(root->versions[1].sha1);
                                return 1;
                        }
@@@ -1641,6 -1640,8 +1641,8 @@@ static int tree_content_get
                n = slash1 - p;
        else
                n = strlen(p);
+       if (!n)
+               die("Empty path component found in input");
  
        if (!root->tree)
                load_tree(root);
@@@ -1982,41 -1983,32 +1984,41 @@@ static int validate_raw_date(const cha
  
  static char *parse_ident(const char *buf)
  {
 -      const char *gt;
 +      const char *ltgt;
        size_t name_len;
        char *ident;
  
 -      gt = strrchr(buf, '>');
 -      if (!gt)
 +      /* ensure there is a space delimiter even if there is no name */
 +      if (*buf == '<')
 +              --buf;
 +
 +      ltgt = buf + strcspn(buf, "<>");
 +      if (*ltgt != '<')
 +              die("Missing < in ident string: %s", buf);
 +      if (ltgt != buf && ltgt[-1] != ' ')
 +              die("Missing space before < in ident string: %s", buf);
 +      ltgt = ltgt + 1 + strcspn(ltgt + 1, "<>");
 +      if (*ltgt != '>')
                die("Missing > in ident string: %s", buf);
 -      gt++;
 -      if (*gt != ' ')
 +      ltgt++;
 +      if (*ltgt != ' ')
                die("Missing space after > in ident string: %s", buf);
 -      gt++;
 -      name_len = gt - buf;
 +      ltgt++;
 +      name_len = ltgt - buf;
        ident = xmalloc(name_len + 24);
        strncpy(ident, buf, name_len);
  
        switch (whenspec) {
        case WHENSPEC_RAW:
 -              if (validate_raw_date(gt, ident + name_len, 24) < 0)
 -                      die("Invalid raw date \"%s\" in ident: %s", gt, buf);
 +              if (validate_raw_date(ltgt, ident + name_len, 24) < 0)
 +                      die("Invalid raw date \"%s\" in ident: %s", ltgt, buf);
                break;
        case WHENSPEC_RFC2822:
 -              if (parse_date(gt, ident + name_len, 24) < 0)
 -                      die("Invalid rfc2822 date \"%s\" in ident: %s", gt, buf);
 +              if (parse_date(ltgt, ident + name_len, 24) < 0)
 +                      die("Invalid rfc2822 date \"%s\" in ident: %s", ltgt, buf);
                break;
        case WHENSPEC_NOW:
 -              if (strcmp("now", gt))
 +              if (strcmp("now", ltgt))
                        die("Date in ident must be 'now': %s", buf);
                datestamp(ident + name_len, 24);
                break;
@@@ -2164,11 -2156,6 +2166,11 @@@ static uintmax_t do_change_note_fanout
  
                if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) {
                        /* This is a note entry */
 +                      if (fanout == 0xff) {
 +                              /* Counting mode, no rename */
 +                              num_notes++;
 +                              continue;
 +                      }
                        construct_path_with_fanout(hex_sha1, fanout, realpath);
                        if (!strcmp(fullpath, realpath)) {
                                /* Note entry is in correct location */
@@@ -2375,7 -2362,7 +2377,7 @@@ static void file_change_cr(struct branc
                leaf.tree);
  }
  
 -static void note_change_n(struct branch *b, unsigned char old_fanout)
 +static void note_change_n(struct branch *b, unsigned char *old_fanout)
  {
        const char *p = command_buf.buf + 2;
        static struct strbuf uq = STRBUF_INIT;
        uint16_t inline_data = 0;
        unsigned char new_fanout;
  
 +      /*
 +       * When loading a branch, we don't traverse its tree to count the real
 +       * number of notes (too expensive to do this for all non-note refs).
 +       * This means that recently loaded notes refs might incorrectly have
 +       * b->num_notes == 0, and consequently, old_fanout might be wrong.
 +       *
 +       * Fix this by traversing the tree and counting the number of notes
 +       * when b->num_notes == 0. If the notes tree is truly empty, the
 +       * calculation should not take long.
 +       */
 +      if (b->num_notes == 0 && *old_fanout == 0) {
 +              /* Invoke change_note_fanout() in "counting mode". */
 +              b->num_notes = change_note_fanout(&b->branch_tree, 0xff);
 +              *old_fanout = convert_num_notes_to_fanout(b->num_notes);
 +      }
 +
 +      /* Now parse the notemodify command. */
        /* <dataref> or 'inline' */
        if (*p == ':') {
                char *x;
        /* <committish> */
        s = lookup_branch(p);
        if (s) {
 +              if (is_null_sha1(s->sha1))
 +                      die("Can't add a note on empty branch.");
                hashcpy(commit_sha1, s->sha1);
        } else if (*p == ':') {
                uintmax_t commit_mark = strtoumax(p + 1, NULL, 10);
                            typename(type), command_buf.buf);
        }
  
 -      construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path);
 +      construct_path_with_fanout(sha1_to_hex(commit_sha1), *old_fanout, path);
        if (tree_content_remove(&b->branch_tree, path, NULL))
                b->num_notes--;
  
@@@ -2650,7 -2618,7 +2652,7 @@@ static void parse_new_commit(void
                else if (!prefixcmp(command_buf.buf, "C "))
                        file_change_cr(b, 0);
                else if (!prefixcmp(command_buf.buf, "N "))
 -                      note_change_n(b, prev_fanout);
 +                      note_change_n(b, &prev_fanout);
                else if (!strcmp("deleteall", command_buf.buf))
                        file_change_deleteall(b);
                else if (!prefixcmp(command_buf.buf, "ls "))
@@@ -2712,7 -2680,7 +2714,7 @@@ static void parse_new_tag(void
        /* Obtain the new tag name from the rest of our command */
        sp = strchr(command_buf.buf, ' ') + 1;
        t = pool_alloc(sizeof(struct tag));
 -      t->next_tag = NULL;
 +      memset(t, 0, sizeof(struct tag));
        t->name = pool_strdup(sp);
        if (last_tag)
                last_tag->next_tag = t;
        from = strchr(command_buf.buf, ' ') + 1;
        s = lookup_branch(from);
        if (s) {
 +              if (is_null_sha1(s->sha1))
 +                      die("Can't tag an empty branch.");
                hashcpy(sha1, s->sha1);
                type = OBJ_COMMIT;
        } else if (*from == ':') {
                type = oe->type;
                hashcpy(sha1, oe->idx.sha1);
        } else if (!get_sha1(from, sha1)) {
 -              unsigned long size;
 -              char *buf;
 -
 -              buf = read_sha1_file(sha1, &type, &size);
 -              if (!buf || size < 46)
 -                      die("Not a valid commit: %s", from);
 -              free(buf);
 +              struct object_entry *oe = find_object(sha1);
 +              if (!oe) {
 +                      type = sha1_object_info(sha1, NULL);
 +                      if (type < 0)
 +                              die("Not a valid object: %s", from);
 +              } else
 +                      type = oe->type;
        } else
                die("Invalid ref name or SHA1 expression: %s", from);
        read_next_command();
@@@ -2848,12 -2814,7 +2850,12 @@@ static void cat_blob(struct object_entr
        strbuf_release(&line);
        cat_blob_write(buf, size);
        cat_blob_write("\n", 1);
 -      free(buf);
 +      if (oe && oe->pack_id == pack_id) {
 +              last_blob.offset = oe->idx.offset;
 +              strbuf_attach(&last_blob.data, buf, size, size);
 +              last_blob.depth = oe->depth;
 +      } else
 +              free(buf);
  }
  
  static void parse_cat_blob(void)
@@@ -2889,7 -2850,7 +2891,7 @@@ static struct object_entry *dereference
                                        unsigned char sha1[20])
  {
        unsigned long size;
 -      void *buf = NULL;
 +      char *buf = NULL;
        if (!oe) {
                enum object_type type = sha1_object_info(sha1, NULL);
                if (type < 0)
@@@ -2982,7 -2943,7 +2984,7 @@@ static void print_ls(int mode, const un
                /* mode SP type SP object_name TAB path LF */
                strbuf_reset(&line);
                strbuf_addf(&line, "%06o %s %s\t",
 -                              mode, type, sha1_to_hex(sha1));
 +                              mode & ~NO_DELTA, type, sha1_to_hex(sha1));
                quote_c_style(path, &line, NULL, 0);
                strbuf_addch(&line, '\n');
        }
@@@ -2993,7 -2954,7 +2995,7 @@@ static void parse_ls(struct branch *b
  {
        const char *p;
        struct tree_entry *root = NULL;
 -      struct tree_entry leaf = {0};
 +      struct tree_entry leaf = {NULL};
  
        /* ls SP (<treeish> SP)? <path> */
        p = command_buf.buf + strlen("ls ");
                store_tree(&leaf);
  
        print_ls(leaf.versions[1].mode, leaf.versions[1].sha1, p);
+       if (leaf.tree)
+               release_tree_content_recursive(leaf.tree);
        if (!b || root != &b->branch_tree)
                release_tree_entry(root);
  }
@@@ -3190,13 -3153,11 +3194,13 @@@ static int parse_one_feature(const cha
                option_export_marks(feature + 13);
        } else if (!strcmp(feature, "cat-blob")) {
                ; /* Don't die - this feature is supported */
 -      } else if (!prefixcmp(feature, "relative-marks")) {
 +      } else if (!strcmp(feature, "relative-marks")) {
                relative_marks_paths = 1;
 -      } else if (!prefixcmp(feature, "no-relative-marks")) {
 +      } else if (!strcmp(feature, "no-relative-marks")) {
                relative_marks_paths = 0;
 -      } else if (!prefixcmp(feature, "force")) {
 +      } else if (!strcmp(feature, "done")) {
 +              require_explicit_termination = 1;
 +      } else if (!strcmp(feature, "force")) {
                force_update = 1;
        } else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
                ; /* do nothing; we have the feature */
@@@ -3252,16 -3213,20 +3256,16 @@@ static int git_pack_config(const char *
                return 0;
        }
        if (!strcmp(k, "pack.indexversion")) {
 -              pack_idx_default_version = git_config_int(k, v);
 -              if (pack_idx_default_version > 2)
 +              pack_idx_opts.version = git_config_int(k, v);
 +              if (pack_idx_opts.version > 2)
                        die("bad pack.indexversion=%"PRIu32,
 -                          pack_idx_default_version);
 +                          pack_idx_opts.version);
                return 0;
        }
        if (!strcmp(k, "pack.packsizelimit")) {
                max_packsize = git_config_ulong(k, v);
                return 0;
        }
 -      if (!strcmp(k, "core.bigfilethreshold")) {
 -              long n = git_config_int(k, v);
 -              big_file_threshold = 0 < n ? n : 0;
 -      }
        return git_default_config(k, v, cb);
  }
  
@@@ -3305,13 -3270,10 +3309,13 @@@ int main(int argc, const char **argv
  
        git_extract_argv0_path(argv[0]);
  
 +      git_setup_gettext();
 +
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(fast_import_usage);
  
        setup_git_directory();
 +      reset_pack_idx_option(&pack_idx_opts);
        git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
                pack_compression_level = core_compression_level;
                        parse_reset_branch();
                else if (!strcmp("checkpoint", command_buf.buf))
                        parse_checkpoint();
 +              else if (!strcmp("done", command_buf.buf))
 +                      break;
                else if (!prefixcmp(command_buf.buf, "progress "))
                        parse_progress();
                else if (!prefixcmp(command_buf.buf, "feature "))
        if (!seen_data_command)
                parse_argv();
  
 +      if (require_explicit_termination && feof(stdin))
 +              die("stream ends early");
 +
        end_packfile();
  
        dump_branches();
                fprintf(stderr, "---------------------------------------------------------------------\n");
                fprintf(stderr, "Alloc'd objects: %10" PRIuMAX "\n", alloc_count);
                fprintf(stderr, "Total objects:   %10" PRIuMAX " (%10" PRIuMAX " duplicates                  )\n", total_count, duplicate_count);
 -              fprintf(stderr, "      blobs  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]);
 -              fprintf(stderr, "      trees  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]);
 -              fprintf(stderr, "      commits:   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]);
 -              fprintf(stderr, "      tags   :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]);
 +              fprintf(stderr, "      blobs  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB], delta_count_attempts_by_type[OBJ_BLOB]);
 +              fprintf(stderr, "      trees  :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE], delta_count_attempts_by_type[OBJ_TREE]);
 +              fprintf(stderr, "      commits:   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT], delta_count_attempts_by_type[OBJ_COMMIT]);
 +              fprintf(stderr, "      tags   :   %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG], delta_count_attempts_by_type[OBJ_TAG]);
                fprintf(stderr, "Total branches:  %10lu (%10lu loads     )\n", branch_count, branch_load_count);
                fprintf(stderr, "      marks:     %10" PRIuMAX " (%10" PRIuMAX " unique    )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count);
                fprintf(stderr, "      atoms:     %10u\n", atom_cnt);
diff --combined t/t9300-fast-import.sh
index 438aaf6b14b8ae0cb35739d6006eaf646876107c,2cd0f0614d2daa6622161cdc8afce34e22328a97..0f5b5e5964a60f31cdfd6bc456848c2f4b820d0a
@@@ -94,12 -94,6 +94,12 @@@ data <<EO
  An annotated tag without a tagger
  EOF
  
 +tag series-A-blob
 +from :3
 +data <<EOF
 +An annotated tag that annotates a blob.
 +EOF
 +
  INPUT_END
  test_expect_success \
      'A: create pack from stdin' \
@@@ -157,18 -151,6 +157,18 @@@ test_expect_success 'A: verify tag/seri
        test_cmp expect actual
  '
  
 +cat >expect <<EOF
 +object $(git rev-parse refs/heads/master:file3)
 +type blob
 +tag series-A-blob
 +
 +An annotated tag that annotates a blob.
 +EOF
 +test_expect_success 'A: verify tag/series-A-blob' '
 +      git cat-file tag tags/series-A-blob >actual &&
 +      test_cmp expect actual
 +'
 +
  cat >expect <<EOF
  :2 `git rev-parse --verify master:file2`
  :3 `git rev-parse --verify master:file3`
@@@ -187,55 -169,6 +187,55 @@@ test_expect_success 
                </dev/null &&
        test_cmp expect marks.new'
  
 +test_tick
 +new_blob=$(echo testing | git hash-object --stdin)
 +cat >input <<INPUT_END
 +tag series-A-blob-2
 +from $(git rev-parse refs/heads/master:file3)
 +data <<EOF
 +Tag blob by sha1.
 +EOF
 +
 +blob
 +mark :6
 +data <<EOF
 +testing
 +EOF
 +
 +commit refs/heads/new_blob
 +committer  <> 0 +0000
 +data 0
 +M 644 :6 new_blob
 +#pretend we got sha1 from fast-import
 +ls "new_blob"
 +
 +tag series-A-blob-3
 +from $new_blob
 +data <<EOF
 +Tag new_blob.
 +EOF
 +INPUT_END
 +
 +cat >expect <<EOF
 +object $(git rev-parse refs/heads/master:file3)
 +type blob
 +tag series-A-blob-2
 +
 +Tag blob by sha1.
 +object $new_blob
 +type blob
 +tag series-A-blob-3
 +
 +Tag new_blob.
 +EOF
 +
 +test_expect_success \
 +      'A: tag blob by sha1' \
 +      'git fast-import <input &&
 +      git cat-file tag tags/series-A-blob-2 >actual &&
 +      git cat-file tag tags/series-A-blob-3 >>actual &&
 +      test_cmp expect actual'
 +
  test_tick
  cat >input <<INPUT_END
  commit refs/heads/verify--import-marks
@@@ -391,105 -324,6 +391,105 @@@ test_expect_success 
         test `git rev-parse master` = `git rev-parse TEMP_TAG^`'
  rm -f .git/TEMP_TAG
  
 +git gc 2>/dev/null >/dev/null
 +git prune 2>/dev/null >/dev/null
 +
 +cat >input <<INPUT_END
 +commit refs/heads/empty-committer-1
 +committer  <> $GIT_COMMITTER_DATE
 +data <<COMMIT
 +empty commit
 +COMMIT
 +INPUT_END
 +test_expect_success 'B: accept empty committer' '
 +      git fast-import <input &&
 +      out=$(git fsck) &&
 +      echo "$out" &&
 +      test -z "$out"
 +'
 +git update-ref -d refs/heads/empty-committer-1 || true
 +
 +git gc 2>/dev/null >/dev/null
 +git prune 2>/dev/null >/dev/null
 +
 +cat >input <<INPUT_END
 +commit refs/heads/empty-committer-2
 +committer <a@b.com> $GIT_COMMITTER_DATE
 +data <<COMMIT
 +empty commit
 +COMMIT
 +INPUT_END
 +test_expect_success 'B: accept and fixup committer with no name' '
 +      git fast-import <input &&
 +      out=$(git fsck) &&
 +      echo "$out" &&
 +      test -z "$out"
 +'
 +git update-ref -d refs/heads/empty-committer-2 || true
 +
 +git gc 2>/dev/null >/dev/null
 +git prune 2>/dev/null >/dev/null
 +
 +cat >input <<INPUT_END
 +commit refs/heads/invalid-committer
 +committer Name email> $GIT_COMMITTER_DATE
 +data <<COMMIT
 +empty commit
 +COMMIT
 +INPUT_END
 +test_expect_success 'B: fail on invalid committer (1)' '
 +      test_must_fail git fast-import <input
 +'
 +git update-ref -d refs/heads/invalid-committer || true
 +
 +cat >input <<INPUT_END
 +commit refs/heads/invalid-committer
 +committer Name <e<mail> $GIT_COMMITTER_DATE
 +data <<COMMIT
 +empty commit
 +COMMIT
 +INPUT_END
 +test_expect_success 'B: fail on invalid committer (2)' '
 +      test_must_fail git fast-import <input
 +'
 +git update-ref -d refs/heads/invalid-committer || true
 +
 +cat >input <<INPUT_END
 +commit refs/heads/invalid-committer
 +committer Name <email>> $GIT_COMMITTER_DATE
 +data <<COMMIT
 +empty commit
 +COMMIT
 +INPUT_END
 +test_expect_success 'B: fail on invalid committer (3)' '
 +      test_must_fail git fast-import <input
 +'
 +git update-ref -d refs/heads/invalid-committer || true
 +
 +cat >input <<INPUT_END
 +commit refs/heads/invalid-committer
 +committer Name <email $GIT_COMMITTER_DATE
 +data <<COMMIT
 +empty commit
 +COMMIT
 +INPUT_END
 +test_expect_success 'B: fail on invalid committer (4)' '
 +      test_must_fail git fast-import <input
 +'
 +git update-ref -d refs/heads/invalid-committer || true
 +
 +cat >input <<INPUT_END
 +commit refs/heads/invalid-committer
 +committer Name<email> $GIT_COMMITTER_DATE
 +data <<COMMIT
 +empty commit
 +COMMIT
 +INPUT_END
 +test_expect_success 'B: fail on invalid committer (5)' '
 +      test_must_fail git fast-import <input
 +'
 +git update-ref -d refs/heads/invalid-committer || true
 +
  ###
  ### series C
  ###
@@@ -820,18 -654,6 +820,18 @@@ test_expect_success 
        'test 1 = `git rev-list J | wc -l` &&
         test 0 = `git ls-tree J | wc -l`'
  
 +cat >input <<INPUT_END
 +reset refs/heads/J2
 +
 +tag wrong_tag
 +from refs/heads/J2
 +data <<EOF
 +Tag branch that was reset.
 +EOF
 +INPUT_END
 +test_expect_success \
 +      'J: tag must fail on empty branch' \
 +      'test_must_fail git fast-import <input'
  ###
  ### series K
  ###
@@@ -912,47 -734,6 +912,47 @@@ test_expect_success 
         git diff-tree --abbrev --raw L^ L >output &&
         test_cmp expect output'
  
 +cat >input <<INPUT_END
 +blob
 +mark :1
 +data <<EOF
 +the data
 +EOF
 +
 +commit refs/heads/L2
 +committer C O Mitter <committer@example.com> 1112912473 -0700
 +data <<COMMIT
 +init L2
 +COMMIT
 +M 644 :1 a/b/c
 +M 644 :1 a/b/d
 +M 644 :1 a/e/f
 +
 +commit refs/heads/L2
 +committer C O Mitter <committer@example.com> 1112912473 -0700
 +data <<COMMIT
 +update L2
 +COMMIT
 +C a g
 +C a/e g/b
 +M 644 :1 g/b/h
 +INPUT_END
 +
 +cat <<EOF >expect
 +g/b/f
 +g/b/h
 +EOF
 +
 +test_expect_success \
 +    'L: nested tree copy does not corrupt deltas' \
 +      'git fast-import <input &&
 +      git ls-tree L2 g/b/ >tmp &&
 +      cat tmp | cut -f 2 >actual &&
 +      test_cmp expect actual &&
 +      git fsck `git rev-parse L2`'
 +
 +git update-ref -d refs/heads/L2
 +
  ###
  ### series M
  ###
@@@ -1306,6 -1087,45 +1306,45 @@@ test_expect_success 
        M 040000 $subdir file3/
        INPUT_END'
  
+ test_expect_success \
+       'N: reject foo/ syntax in copy source' \
+       'test_must_fail git fast-import <<-INPUT_END
+       commit refs/heads/N5C
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy with invalid syntax
+       COMMIT
+       from refs/heads/branch^0
+       C file2/ file3
+       INPUT_END'
+ test_expect_success \
+       'N: reject foo/ syntax in rename source' \
+       'test_must_fail git fast-import <<-INPUT_END
+       commit refs/heads/N5D
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       rename with invalid syntax
+       COMMIT
+       from refs/heads/branch^0
+       R file2/ file3
+       INPUT_END'
+ test_expect_success \
+       'N: reject foo/ syntax in ls argument' \
+       'test_must_fail git fast-import <<-INPUT_END
+       commit refs/heads/N5E
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       copy with invalid syntax
+       COMMIT
+       from refs/heads/branch^0
+       ls "file2/"
+       INPUT_END'
  test_expect_success \
        'N: copy to root by id and modify' \
        'echo "hello, world" >expect.foo &&
@@@ -1987,23 -1807,6 +2026,23 @@@ test_expect_success 
        'Q: verify second note for second commit' \
        'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'
  
 +cat >input <<EOF
 +reset refs/heads/Q0
 +
 +commit refs/heads/note-Q0
 +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +data <<COMMIT
 +Note for an empty branch.
 +COMMIT
 +
 +N inline refs/heads/Q0
 +data <<NOTE
 +some note
 +NOTE
 +EOF
 +test_expect_success \
 +      'Q: deny note on empty branch' \
 +      'test_must_fail git fast-import <input'
  ###
  ### series R (feature and option)
  ###
@@@ -2118,53 -1921,6 +2157,53 @@@ test_expect_success 'R: --import-marks-
        test_cmp expect io.marks
  '
  
 +test_expect_success 'R: feature import-marks-if-exists' '
 +      rm -f io.marks &&
 +      >expect &&
 +
 +      git fast-import --export-marks=io.marks <<-\EOF &&
 +      feature import-marks-if-exists=not_io.marks
 +      EOF
 +      test_cmp expect io.marks &&
 +
 +      blob=$(echo hi | git hash-object --stdin) &&
 +
 +      echo ":1 $blob" >io.marks &&
 +      echo ":1 $blob" >expect &&
 +      echo ":2 $blob" >>expect &&
 +
 +      git fast-import --export-marks=io.marks <<-\EOF &&
 +      feature import-marks-if-exists=io.marks
 +      blob
 +      mark :2
 +      data 3
 +      hi
 +
 +      EOF
 +      test_cmp expect io.marks &&
 +
 +      echo ":3 $blob" >>expect &&
 +
 +      git fast-import --import-marks=io.marks \
 +                      --export-marks=io.marks <<-\EOF &&
 +      feature import-marks-if-exists=not_io.marks
 +      blob
 +      mark :3
 +      data 3
 +      hi
 +
 +      EOF
 +      test_cmp expect io.marks &&
 +
 +      >expect &&
 +
 +      git fast-import --import-marks-if-exists=not_io.marks \
 +                      --export-marks=io.marks <<-\EOF
 +      feature import-marks-if-exists=io.marks
 +      EOF
 +      test_cmp expect io.marks
 +'
 +
  cat >input << EOF
  feature import-marks=marks.out
  feature export-marks=marks.new
@@@ -2176,7 -1932,7 +2215,7 @@@ test_expect_success 
      test_cmp marks.out marks.new'
  
  cat >input <<EOF
 -feature import-marks=nonexistant.marks
 +feature import-marks=nonexistent.marks
  feature export-marks=marks.new
  EOF
  
@@@ -2187,7 -1943,7 +2226,7 @@@ test_expect_success 
  
  
  cat >input <<EOF
 -feature import-marks=nonexistant.marks
 +feature import-marks=nonexistent.marks
  feature export-marks=combined.marks
  EOF
  
@@@ -2237,7 -1993,7 +2276,7 @@@ test_expect_success 'R: cat-blob-fd mus
        test_must_fail git fast-import --cat-blob-fd=-1 </dev/null
  '
  
 -test_expect_success 'R: print old blob' '
 +test_expect_success NOT_MINGW 'R: print old blob' '
        blob=$(echo "yes it can" | git hash-object -w --stdin) &&
        cat >expect <<-EOF &&
        ${blob} blob 11
        test_cmp expect actual
  '
  
 -test_expect_success 'R: in-stream cat-blob-fd not respected' '
 +test_expect_success NOT_MINGW 'R: in-stream cat-blob-fd not respected' '
        echo hello >greeting &&
        blob=$(git hash-object -w greeting) &&
        cat >expect <<-EOF &&
        test_cmp expect actual.1
  '
  
 -test_expect_success 'R: print new blob' '
 +test_expect_success NOT_MINGW 'R: print new blob' '
        blob=$(echo "yep yep yep" | git hash-object --stdin) &&
        cat >expect <<-EOF &&
        ${blob} blob 12
        test_cmp expect actual
  '
  
 -test_expect_success 'R: print new blob by sha1' '
 +test_expect_success NOT_MINGW 'R: print new blob by sha1' '
        blob=$(echo "a new blob named by sha1" | git hash-object --stdin) &&
        cat >expect <<-EOF &&
        ${blob} blob 25
@@@ -2480,48 -2236,6 +2519,48 @@@ test_expect_success 'R: quiet option re
      test_cmp empty output
  '
  
 +test_expect_success 'R: feature done means terminating "done" is mandatory' '
 +      echo feature done | test_must_fail git fast-import &&
 +      test_must_fail git fast-import --done </dev/null
 +'
 +
 +test_expect_success 'R: terminating "done" with trailing gibberish is ok' '
 +      git fast-import <<-\EOF &&
 +      feature done
 +      done
 +      trailing gibberish
 +      EOF
 +      git fast-import <<-\EOF
 +      done
 +      more trailing gibberish
 +      EOF
 +'
 +
 +test_expect_success 'R: terminating "done" within commit' '
 +      cat >expect <<-\EOF &&
 +      OBJID
 +      :000000 100644 OBJID OBJID A    hello.c
 +      :000000 100644 OBJID OBJID A    hello2.c
 +      EOF
 +      git fast-import <<-EOF &&
 +      commit refs/heads/done-ends
 +      committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +      data <<EOT
 +      Commit terminated by "done" command
 +      EOT
 +      M 100644 inline hello.c
 +      data <<EOT
 +      Hello, world.
 +      EOT
 +      C hello.c hello2.c
 +      done
 +      EOF
 +      git rev-list done-ends |
 +      git diff-tree -r --stdin --root --always |
 +      sed -e "s/$_x40/OBJID/g" >actual &&
 +      test_cmp expect actual
 +'
 +
  cat >input <<EOF
  option git non-existing-option
  EOF