Merge branch 'di/fast-import-tagging'
authorJunio C Hamano <gitster@pobox.com>
Mon, 29 Aug 2011 04:18:48 +0000 (21:18 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 29 Aug 2011 04:18:48 +0000 (21:18 -0700)
* di/fast-import-tagging:
fast-import: allow to tag newly created objects
fast-import: add tests for tagging blobs

1  2 
fast-import.c
t/t9300-fast-import.sh
diff --combined fast-import.c
index 96ccd2aece73d903d52aa15dc4ef1208251cff49,4f56f772a33c353b296223123e7e0d5307ec0a48..742e7da6b8b58dd0803d89c6ad6c59589274a31b
@@@ -170,11 -170,6 +170,11 @@@ Format of STDIN stream
  #define DEPTH_BITS 13
  #define MAX_DEPTH ((1<<DEPTH_BITS)-1)
  
 +/*
 + * 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;
@@@ -289,7 -284,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 -304,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 -354,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;
@@@ -904,7 -896,7 +904,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;
  }
@@@ -1025,7 -1017,7 +1025,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);
                }
        }
  
@@@ -1172,7 -1163,7 +1172,7 @@@ static void stream_blob(uintmax_t len, 
        off_t offset;
        git_SHA_CTX c;
        git_SHA_CTX pack_file_ctx;
 -      z_stream s;
 +      git_zstream s;
        int status = Z_OK;
  
        /* Determine if we should auto-checkpoint. */
        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)
@@@ -1423,9 -1414,8 +1423,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);
        }
  }
@@@ -1435,7 -1425,7 +1435,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;
@@@ -1480,7 -1469,6 +1480,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);
@@@ -1525,23 -1513,6 +1525,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;
                        }
@@@ -1996,41 -1967,32 +1996,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;
@@@ -2726,13 -2688,13 +2726,13 @@@ static void parse_new_tag(void
                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();
@@@ -2836,12 -2798,7 +2836,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)
@@@ -2970,7 -2927,7 +2970,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');
        }
@@@ -3182,8 -3139,6 +3182,8 @@@ static int parse_one_feature(const cha
                relative_marks_paths = 1;
        } else if (!strcmp(feature, "no-relative-marks")) {
                relative_marks_paths = 0;
 +      } 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")) {
@@@ -3240,10 -3195,10 +3240,10 @@@ 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")) {
@@@ -3297,7 -3252,6 +3297,7 @@@ int main(int argc, const char **argv
                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 dbe109963e74ed1819811c3a8df69140903eb185,ab9493478cdaf4036bf1b79988cc2fac0fa22f4b..1a6c06631c1c2a8fc29b66c9265541b54e8a9715
@@@ -94,6 -94,12 +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' \
@@@ -151,6 -157,18 +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`
@@@ -169,6 -187,55 +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
@@@ -324,105 -391,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
  ###
@@@ -833,47 -801,6 +900,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
  ###
@@@ -2022,53 -1949,6 +2089,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
@@@ -2384,48 -2264,6 +2451,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