Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Thu, 27 Jan 2011 18:27:49 +0000 (10:27 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 27 Jan 2011 18:27:49 +0000 (10:27 -0800)
* maint:
rebase -i: clarify in-editor documentation of "exec"
tests: sanitize more git environment variables
fast-import: treat filemodify with empty tree as delete
rebase: give a better error message for bogus branch
rebase: use explicit "--" with checkout

Conflicts:
t/t9300-fast-import.sh

1  2 
fast-import.c
git-rebase--interactive.sh
git-rebase.sh
t/t9300-fast-import.sh
t/test-lib.sh
diff --combined fast-import.c
index 785776086ccc22dcd5488e3ac0bad3318f57043e,7563e43a39c4d4573218fab52ffbf64a255f2562..60f26fe473ad6922166e952f4f7e5be2b79d45de
@@@ -132,17 -132,14 +132,17 @@@ Format of STDIN stream
    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.
 +     # note: comments and cat requests 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 line (an lf
       # preceded it).
       #
 +  cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf;
 +
    comment ::= '#' not_lf* lf;
    not_lf  ::= # Any byte that is not ASCII newline (LF);
  */
  #include "csum-file.h"
  #include "quote.h"
  #include "exec_cmd.h"
 +#include "dir.h"
  
  #define PACK_ID_BITS 16
  #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
@@@ -365,14 -361,7 +365,14 @@@ static uintmax_t next_mark
  static struct strbuf new_data = STRBUF_INIT;
  static int seen_data_command;
  
 +/* Signal handling */
 +static volatile sig_atomic_t checkpoint_requested;
 +
 +/* Where to write output of cat-blob commands */
 +static int cat_blob_fd = STDOUT_FILENO;
 +
  static void parse_argv(void);
 +static void parse_cat_blob(void);
  
  static void write_branch_report(FILE *rpt, struct branch *b)
  {
@@@ -511,32 -500,6 +511,32 @@@ static NORETURN void die_nicely(const c
        exit(128);
  }
  
 +#ifndef SIGUSR1       /* Windows, for example */
 +
 +static void set_checkpoint_signal(void)
 +{
 +}
 +
 +#else
 +
 +static void checkpoint_signal(int signo)
 +{
 +      checkpoint_requested = 1;
 +}
 +
 +static void set_checkpoint_signal(void)
 +{
 +      struct sigaction sa;
 +
 +      memset(&sa, 0, sizeof(sa));
 +      sa.sa_handler = checkpoint_signal;
 +      sigemptyset(&sa.sa_mask);
 +      sa.sa_flags = SA_RESTART;
 +      sigaction(SIGUSR1, &sa, NULL);
 +}
 +
 +#endif
 +
  static void alloc_objects(unsigned int cnt)
  {
        struct object_entry_pool *b;
@@@ -1469,20 -1432,6 +1469,20 @@@ static void store_tree(struct tree_entr
        t->entry_count -= del;
  }
  
 +static void tree_content_replace(
 +      struct tree_entry *root,
 +      const unsigned char *sha1,
 +      const uint16_t mode,
 +      struct tree_content *newtree)
 +{
 +      if (!S_ISDIR(mode))
 +              die("Root cannot be a non-directory");
 +      hashcpy(root->versions[1].sha1, sha1);
 +      if (root->tree)
 +              release_tree_content_recursive(root->tree);
 +      root->tree = newtree;
 +}
 +
  static int tree_content_set(
        struct tree_entry *root,
        const char *p,
        const uint16_t mode,
        struct tree_content *subtree)
  {
 -      struct tree_content *t = root->tree;
 +      struct tree_content *t;
        const char *slash1;
        unsigned int i, n;
        struct tree_entry *e;
        if (!slash1 && !S_ISDIR(mode) && subtree)
                die("Non-directories cannot have subtrees");
  
 +      if (!root->tree)
 +              load_tree(root);
 +      t = root->tree;
        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 (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
                        if (!slash1) {
                                if (!S_ISDIR(mode)
                                                && e->versions[1].mode == mode
@@@ -1563,7 -1509,7 +1563,7 @@@ static int tree_content_remove
        const char *p,
        struct tree_entry *backup_leaf)
  {
 -      struct tree_content *t = root->tree;
 +      struct tree_content *t;
        const char *slash1;
        unsigned int i, n;
        struct tree_entry *e;
        else
                n = strlen(p);
  
 +      if (!root->tree)
 +              load_tree(root);
 +      t = root->tree;
        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 (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
                        if (slash1 && !S_ISDIR(e->versions[1].mode))
                                /*
                                 * If p names a file in some subdirectory, and a
@@@ -1624,7 -1567,7 +1624,7 @@@ static int tree_content_get
        const char *p,
        struct tree_entry *leaf)
  {
 -      struct tree_content *t = root->tree;
 +      struct tree_content *t;
        const char *slash1;
        unsigned int i, n;
        struct tree_entry *e;
        else
                n = strlen(p);
  
 +      if (!root->tree)
 +              load_tree(root);
 +      t = root->tree;
        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 (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
                        if (!slash1) {
                                memcpy(leaf, e, sizeof(*leaf));
                                if (e->tree && is_null_sha1(e->versions[1].sha1))
@@@ -1836,7 -1776,7 +1836,7 @@@ static int read_next_command(void
                return EOF;
        }
  
 -      do {
 +      for (;;) {
                if (unread_command_buf) {
                        unread_command_buf = 0;
                } else {
                        rc->prev->next = rc;
                        cmd_tail = rc;
                }
 -      } while (command_buf.buf[0] == '#');
 -
 -      return 0;
 +              if (!prefixcmp(command_buf.buf, "cat-blob ")) {
 +                      parse_cat_blob();
 +                      continue;
 +              }
 +              if (command_buf.buf[0] == '#')
 +                      continue;
 +              return 0;
 +      }
  }
  
  static void skip_optional_lf(void)
@@@ -2231,6 -2166,12 +2231,12 @@@ static void file_change_m(struct branc
                p = uq.buf;
        }
  
+       /* Git does not track empty, non-toplevel directories. */
+       if (S_ISDIR(mode) && !memcmp(sha1, EMPTY_TREE_SHA1_BIN, 20) && *p) {
+               tree_content_remove(&b->branch_tree, p, NULL);
+               return;
+       }
        if (S_ISGITLINK(mode)) {
                if (inline_data)
                        die("Git links cannot be specified 'inline': %s",
                                command_buf.buf);
        }
  
 +      if (!*p) {
 +              tree_content_replace(&b->branch_tree, sha1, mode, NULL);
 +              return;
 +      }
        tree_content_set(&b->branch_tree, p, sha1, mode, NULL);
  }
  
@@@ -2331,13 -2268,6 +2337,13 @@@ static void file_change_cr(struct branc
                tree_content_get(&b->branch_tree, s, &leaf);
        if (!leaf.versions[1].mode)
                die("Path %s not in branch", s);
 +      if (!*d) {      /* C "path/to/subdir" "" */
 +              tree_content_replace(&b->branch_tree,
 +                      leaf.versions[1].sha1,
 +                      leaf.versions[1].mode,
 +                      leaf.tree);
 +              return;
 +      }
        tree_content_set(&b->branch_tree, d,
                leaf.versions[1].sha1,
                leaf.versions[1].mode,
@@@ -2751,95 -2681,14 +2757,95 @@@ static void parse_reset_branch(void
                unread_command_buf = 1;
  }
  
 -static void parse_checkpoint(void)
 +static void cat_blob_write(const char *buf, unsigned long size)
 +{
 +      if (write_in_full(cat_blob_fd, buf, size) != size)
 +              die_errno("Write to frontend failed");
 +}
 +
 +static void cat_blob(struct object_entry *oe, unsigned char sha1[20])
 +{
 +      struct strbuf line = STRBUF_INIT;
 +      unsigned long size;
 +      enum object_type type = 0;
 +      char *buf;
 +
 +      if (!oe || oe->pack_id == MAX_PACK_ID) {
 +              buf = read_sha1_file(sha1, &type, &size);
 +      } else {
 +              type = oe->type;
 +              buf = gfi_unpack_entry(oe, &size);
 +      }
 +
 +      /*
 +       * Output based on batch_one_object() from cat-file.c.
 +       */
 +      if (type <= 0) {
 +              strbuf_reset(&line);
 +              strbuf_addf(&line, "%s missing\n", sha1_to_hex(sha1));
 +              cat_blob_write(line.buf, line.len);
 +              strbuf_release(&line);
 +              free(buf);
 +              return;
 +      }
 +      if (!buf)
 +              die("Can't read object %s", sha1_to_hex(sha1));
 +      if (type != OBJ_BLOB)
 +              die("Object %s is a %s but a blob was expected.",
 +                  sha1_to_hex(sha1), typename(type));
 +      strbuf_reset(&line);
 +      strbuf_addf(&line, "%s %s %lu\n", sha1_to_hex(sha1),
 +                                              typename(type), size);
 +      cat_blob_write(line.buf, line.len);
 +      strbuf_release(&line);
 +      cat_blob_write(buf, size);
 +      cat_blob_write("\n", 1);
 +      free(buf);
 +}
 +
 +static void parse_cat_blob(void)
  {
 +      const char *p;
 +      struct object_entry *oe = oe;
 +      unsigned char sha1[20];
 +
 +      /* cat-blob SP <object> LF */
 +      p = command_buf.buf + strlen("cat-blob ");
 +      if (*p == ':') {
 +              char *x;
 +              oe = find_mark(strtoumax(p + 1, &x, 10));
 +              if (x == p + 1)
 +                      die("Invalid mark: %s", command_buf.buf);
 +              if (!oe)
 +                      die("Unknown mark: %s", command_buf.buf);
 +              if (*x)
 +                      die("Garbage after mark: %s", command_buf.buf);
 +              hashcpy(sha1, oe->idx.sha1);
 +      } else {
 +              if (get_sha1_hex(p, sha1))
 +                      die("Invalid SHA1: %s", command_buf.buf);
 +              if (p[40])
 +                      die("Garbage after SHA1: %s", command_buf.buf);
 +              oe = find_object(sha1);
 +      }
 +
 +      cat_blob(oe, sha1);
 +}
 +
 +static void checkpoint(void)
 +{
 +      checkpoint_requested = 0;
        if (object_count) {
                cycle_packfile();
                dump_branches();
                dump_tags();
                dump_marks();
        }
 +}
 +
 +static void parse_checkpoint(void)
 +{
 +      checkpoint_requested = 1;
        skip_optional_lf();
  }
  
@@@ -2889,25 -2738,16 +2895,25 @@@ static void option_date_format(const ch
                die("unknown --date-format argument %s", fmt);
  }
  
 +static unsigned long ulong_arg(const char *option, const char *arg)
 +{
 +      char *endptr;
 +      unsigned long rv = strtoul(arg, &endptr, 0);
 +      if (strchr(arg, '-') || endptr == arg || *endptr)
 +              die("%s: argument must be a non-negative integer", option);
 +      return rv;
 +}
 +
  static void option_depth(const char *depth)
  {
 -      max_depth = strtoul(depth, NULL, 0);
 +      max_depth = ulong_arg("--depth", depth);
        if (max_depth > MAX_DEPTH)
                die("--depth cannot exceed %u", MAX_DEPTH);
  }
  
  static void option_active_branches(const char *branches)
  {
 -      max_active_branches = strtoul(branches, NULL, 0);
 +      max_active_branches = ulong_arg("--active-branches", branches);
  }
  
  static void option_export_marks(const char *marks)
        safe_create_leading_directories_const(export_marks_file);
  }
  
 +static void option_cat_blob_fd(const char *fd)
 +{
 +      unsigned long n = ulong_arg("--cat-blob-fd", fd);
 +      if (n > (unsigned long) INT_MAX)
 +              die("--cat-blob-fd cannot exceed %d", INT_MAX);
 +      cat_blob_fd = (int) n;
 +}
 +
  static void option_export_pack_edges(const char *edges)
  {
        if (pack_edges)
@@@ -2977,8 -2809,6 +2983,8 @@@ static int parse_one_feature(const cha
                option_import_marks(feature + 13, from_stream);
        } else if (!prefixcmp(feature, "export-marks=")) {
                option_export_marks(feature + 13);
 +      } else if (!strcmp(feature, "cat-blob")) {
 +              ; /* Don't die - this feature is supported */
        } else if (!prefixcmp(feature, "relative-marks")) {
                relative_marks_paths = 1;
        } else if (!prefixcmp(feature, "no-relative-marks")) {
@@@ -3073,11 -2903,6 +3079,11 @@@ static void parse_argv(void
                if (parse_one_feature(a + 2, 0))
                        continue;
  
 +              if (!prefixcmp(a + 2, "cat-blob-fd=")) {
 +                      option_cat_blob_fd(a + 2 + strlen("cat-blob-fd="));
 +                      continue;
 +              }
 +
                die("unknown option %s", a);
        }
        if (i != global_argc)
@@@ -3120,7 -2945,6 +3126,7 @@@ int main(int argc, const char **argv
        prepare_packed_git();
        start_packfile();
        set_die_routine(die_nicely);
 +      set_checkpoint_signal();
        while (read_next_command() != EOF) {
                if (!strcmp("blob", command_buf.buf))
                        parse_new_blob();
                        /* ignore non-git options*/;
                else
                        die("Unsupported command: %s", command_buf.buf);
 +
 +              if (checkpoint_requested)
 +                      checkpoint();
        }
  
        /* argv hasn't been parsed yet, do so */
index a5ffd9a31eea0f361774d77867c83f444847279b,9f017750ca493fadf2ab1b51fc46f549926fee97..5873ba4bc3b2a94cd7754ae572072fd745dc059d
@@@ -28,7 -28,6 +28,7 @@@ continue           continue rebasing pr
  abort              abort rebasing process and restore original branch
  skip               skip current patch and continue rebasing process
  no-verify          override pre-rebase hook from stopping the operation
 +verify             allow pre-rebase hook to run
  root               rebase all reachable commmits up to the root(s)
  autosquash         move commits that begin with squash!/fixup! under -i
  "
@@@ -154,6 -153,14 +154,6 @@@ run_pre_rebase_hook () 
        fi
  }
  
 -require_clean_work_tree () {
 -      # test if working tree is dirty
 -      git rev-parse --verify HEAD > /dev/null &&
 -      git update-index --ignore-submodules --refresh &&
 -      git diff-files --quiet --ignore-submodules &&
 -      git diff-index --cached --quiet HEAD --ignore-submodules -- ||
 -      die "Working tree is dirty"
 -}
  
  ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
  
@@@ -550,7 -557,7 +550,7 @@@ do_next () 
                        exit "$status"
                fi
                # Run in subshell because require_clean_work_tree can die.
 -              if ! (require_clean_work_tree)
 +              if ! (require_clean_work_tree "rebase")
                then
                        warn "Commit or stash your changes, and then run"
                        warn
@@@ -668,27 -675,9 +668,27 @@@ get_saved_options () 
  # comes immediately after the former, and change "pick" to
  # "fixup"/"squash".
  rearrange_squash () {
 -      sed -n -e 's/^pick \([0-9a-f]*\) \(squash\)! /\1 \2 /p' \
 -              -e 's/^pick \([0-9a-f]*\) \(fixup\)! /\1 \2 /p' \
 -              "$1" >"$1.sq"
 +      # extract fixup!/squash! lines and resolve any referenced sha1's
 +      while read -r pick sha1 message
 +      do
 +              case "$message" in
 +              "squash! "*|"fixup! "*)
 +                      action="${message%%!*}"
 +                      rest="${message#*! }"
 +                      echo "$sha1 $action $rest"
 +                      # if it's a single word, try to resolve to a full sha1 and
 +                      # emit a second copy. This allows us to match on both message
 +                      # and on sha1 prefix
 +                      if test "${rest#* }" = "$rest"; then
 +                              fullsha="$(git rev-parse -q --verify "$rest" 2>/dev/null)"
 +                              if test -n "$fullsha"; then
 +                                      # prefix the action to uniquely identify this line as
 +                                      # intended for full sha1 match
 +                                      echo "$sha1 +$action $fullsha"
 +                              fi
 +                      fi
 +              esac
 +      done >"$1.sq" <"$1"
        test -s "$1.sq" || return
  
        used=
                *" $sha1 "*) continue ;;
                esac
                printf '%s\n' "$pick $sha1 $message"
 +              used="$used$sha1 "
                while read -r squash action msg
                do
 -                      case "$message" in
 -                      "$msg"*)
 +                      case " $used" in
 +                      *" $squash "*) continue ;;
 +                      esac
 +                      emit=0
 +                      case "$action" in
 +                      +*)
 +                              action="${action#+}"
 +                              # full sha1 prefix test
 +                              case "$msg" in "$sha1"*) emit=1;; esac ;;
 +                      *)
 +                              # message prefix test
 +                              case "$message" in "$msg"*) emit=1;; esac ;;
 +                      esac
 +                      if test $emit = 1; then
                                printf '%s\n' "$action $squash $action! $msg"
                                used="$used$squash "
 -                              ;;
 -                      esac
 +                      fi
                done <"$1.sq"
        done >"$1.rearranged" <"$1"
        cat "$1.rearranged" >"$1"
@@@ -750,7 -727,6 +750,7 @@@ d
                OK_TO_SKIP_PRE_REBASE=yes
                ;;
        --verify)
 +              OK_TO_SKIP_PRE_REBASE=
                ;;
        --continue)
                is_standalone "$@" || usage
@@@ -792,7 -768,7 +792,7 @@@ first and then run 'git rebase --contin
  
                record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
  
 -              require_clean_work_tree
 +              require_clean_work_tree "rebase"
                do_rest
                ;;
        --abort)
  
                comment_for_reflog start
  
 -              require_clean_work_tree
 +              require_clean_work_tree "rebase" "Please commit or stash them."
  
                if test ! -z "$1"
                then
-                       output git checkout "$1" ||
+                       output git checkout "$1" -- ||
                                die "Could not checkout $1"
                fi
  
  #  e, edit = use commit, but stop for amending
  #  s, squash = use commit, but meld into previous commit
  #  f, fixup = like "squash", but discard this commit's log message
- #  x <cmd>, exec <cmd> = Run a shell command <cmd>, and stop if it fails
+ #  x, exec = run command (the rest of the line) using shell
  #
  # If you remove a line here THAT COMMIT WILL BE LOST.
  # However, if you remove everything, the rebase will be aborted.
diff --combined git-rebase.sh
index d8e190302668ca352fd58cd052a677347a29cdcd,26d5197a0fb627eb6991910c229eacbf9b190638..cbb0ea90ed410d5af68ce814b7fd93a90829f3ee
@@@ -206,9 -206,6 +206,9 @@@ d
        --no-verify)
                OK_TO_SKIP_PRE_REBASE=yes
                ;;
 +      --verify)
 +              OK_TO_SKIP_PRE_REBASE=
 +              ;;
        --continue)
                test -d "$dotest" -o -d "$GIT_DIR"/rebase-apply ||
                        die "No rebase in progress?"
@@@ -417,7 -414,19 +417,7 @@@ els
        fi
  fi
  
 -# The tree must be really really clean.
 -if ! git update-index --ignore-submodules --refresh > /dev/null; then
 -      echo >&2 "cannot rebase: you have unstaged changes"
 -      git diff-files --name-status -r --ignore-submodules -- >&2
 -      exit 1
 -fi
 -diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)
 -case "$diff" in
 -?*)   echo >&2 "cannot rebase: your index contains uncommitted changes"
 -      echo >&2 "$diff"
 -      exit 1
 -      ;;
 -esac
 +require_clean_work_tree "rebase" "Please commit or stash them."
  
  if test -z "$rebase_root"
  then
@@@ -482,6 -491,7 +482,7 @@@ case "$#" i
        then
                head_name="detached HEAD"
        else
+               echo >&2 "fatal: no such branch: $1"
                usage
        fi
        ;;
@@@ -513,7 -523,7 +514,7 @@@ the
        if test -z "$force_rebase"
        then
                # Lazily switch to the target branch if needed...
-               test -z "$switch_to" || git checkout "$switch_to"
+               test -z "$switch_to" || git checkout "$switch_to" --
                say "Current branch $branch_name is up to date."
                exit 0
        else
diff --combined t/t9300-fast-import.sh
index 222d1059ef99879d3db387be798f973fb093f53e,385e78c22c0e00b9e81117a5ed7378298460c47b..986bc14d58c30201e75f3d95d9b8245e997b99e9
@@@ -7,23 -7,6 +7,23 @@@ test_description='test git fast-import 
  . ./test-lib.sh
  . "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
  
 +# Print $1 bytes from stdin to stdout.
 +#
 +# This could be written as "head -c $1", but IRIX "head" does not
 +# support the -c option.
 +head_c () {
 +      perl -e '
 +              my $len = $ARGV[1];
 +              while ($len > 0) {
 +                      my $s;
 +                      my $nread = sysread(STDIN, $s, $len);
 +                      die "cannot read: $!" unless defined($nread);
 +                      print $s;
 +                      $len -= $nread;
 +              }
 +      ' - "$1"
 +}
 +
  file2_data='file2
  second line of EOF'
  
@@@ -40,18 -23,11 +40,18 @@@ file5_data='an inline file
  file6_data='#!/bin/sh
  echo "$@"'
  
 +>empty
 +
  ###
  ### series A
  ###
  
  test_tick
 +
 +test_expect_success 'empty stream succeeds' '
 +      git fast-import </dev/null
 +'
 +
  cat >input <<INPUT_END
  blob
  mark :2
@@@ -345,7 -321,7 +345,7 @@@ test_expect_success 
        'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
  test_expect_success \
        'C: validate reuse existing blob' \
 -      'test $newf = `git rev-parse --verify branch:file2/newf`
 +      'test $newf = `git rev-parse --verify branch:file2/newf` &&
         test $oldf = `git rev-parse --verify branch:file2/oldf`'
  
  cat >expect <<EOF
@@@ -898,27 -874,48 +898,69 @@@ test_expect_success 
         git diff-tree -C --find-copies-harder -r N4^ N4 >actual &&
         compare_diff_raw expect actual'
  
 +test_expect_success \
 +      'N: copy root directory by tree hash' \
 +      'cat >expect <<-\EOF &&
 +      :100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D      file3/newf
 +      :100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D      file3/oldf
 +      EOF
 +       root=$(git rev-parse refs/heads/branch^0^{tree}) &&
 +       cat >input <<-INPUT_END &&
 +      commit refs/heads/N6
 +      committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +      data <<COMMIT
 +      copy root directory by tree hash
 +      COMMIT
 +
 +      from refs/heads/branch^0
 +      M 040000 $root ""
 +      INPUT_END
 +       git fast-import <input &&
 +       git diff-tree -C --find-copies-harder -r N4 N6 >actual &&
 +       compare_diff_raw expect actual'
 +
+ test_expect_success \
+       'N: delete directory by copying' \
+       'cat >expect <<-\EOF &&
+       OBJID
+       :100644 000000 OBJID OBJID D    foo/bar/qux
+       OBJID
+       :000000 100644 OBJID OBJID A    foo/bar/baz
+       :000000 100644 OBJID OBJID A    foo/bar/qux
+       EOF
+        empty_tree=$(git mktree </dev/null) &&
+        cat >input <<-INPUT_END &&
+       commit refs/heads/N-delete
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       collect data to be deleted
+       COMMIT
+       deleteall
+       M 100644 inline foo/bar/baz
+       data <<DATA_END
+       hello
+       DATA_END
+       C "foo/bar/baz" "foo/bar/qux"
+       C "foo/bar/baz" "foo/bar/quux/1"
+       C "foo/bar/baz" "foo/bar/quuux"
+       M 040000 $empty_tree foo/bar/quux
+       M 040000 $empty_tree foo/bar/quuux
+       commit refs/heads/N-delete
+       committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+       data <<COMMIT
+       delete subdirectory
+       COMMIT
+       M 040000 $empty_tree foo/bar/qux
+       INPUT_END
+        git fast-import <input &&
+        git rev-list N-delete |
+               git diff-tree -r --stdin --root --always |
+               sed -e "s/$_x40/OBJID/g" >actual &&
+        test_cmp expect actual'
  test_expect_success \
        'N: modify copied tree' \
        'cat >expect <<-\EOF &&
         git diff-tree -C --find-copies-harder -r N5^^ N5 >actual &&
         compare_diff_raw expect actual'
  
 +test_expect_success \
 +      'N: reject foo/ syntax' \
 +      'subdir=$(git rev-parse refs/heads/branch^0:file2) &&
 +       test_must_fail git fast-import <<-INPUT_END
 +      commit refs/heads/N5B
 +      committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +      data <<COMMIT
 +      copy with invalid syntax
 +      COMMIT
 +
 +      from refs/heads/branch^0
 +      M 040000 $subdir file3/
 +      INPUT_END'
 +
 +test_expect_success \
 +      'N: copy to root by id and modify' \
 +      'echo "hello, world" >expect.foo &&
 +       echo hello >expect.bar &&
 +       git fast-import <<-SETUP_END &&
 +      commit refs/heads/N7
 +      committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +      data <<COMMIT
 +      hello, tree
 +      COMMIT
 +
 +      deleteall
 +      M 644 inline foo/bar
 +      data <<EOF
 +      hello
 +      EOF
 +      SETUP_END
 +
 +       tree=$(git rev-parse --verify N7:) &&
 +       git fast-import <<-INPUT_END &&
 +      commit refs/heads/N8
 +      committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +      data <<COMMIT
 +      copy to root by id and modify
 +      COMMIT
 +
 +      M 040000 $tree ""
 +      M 644 inline foo/foo
 +      data <<EOF
 +      hello, world
 +      EOF
 +      INPUT_END
 +       git show N8:foo/foo >actual.foo &&
 +       git show N8:foo/bar >actual.bar &&
 +       test_cmp expect.foo actual.foo &&
 +       test_cmp expect.bar actual.bar'
 +
 +test_expect_success \
 +      'N: extract subtree' \
 +      'branch=$(git rev-parse --verify refs/heads/branch^{tree}) &&
 +       cat >input <<-INPUT_END &&
 +      commit refs/heads/N9
 +      committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +      data <<COMMIT
 +      extract subtree branch:newdir
 +      COMMIT
 +
 +      M 040000 $branch ""
 +      C "newdir" ""
 +      INPUT_END
 +       git fast-import <input &&
 +       git diff --exit-code branch:newdir N9'
 +
 +test_expect_success \
 +      'N: modify subtree, extract it, and modify again' \
 +      'echo hello >expect.baz &&
 +       echo hello, world >expect.qux &&
 +       git fast-import <<-SETUP_END &&
 +      commit refs/heads/N10
 +      committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +      data <<COMMIT
 +      hello, tree
 +      COMMIT
 +
 +      deleteall
 +      M 644 inline foo/bar/baz
 +      data <<EOF
 +      hello
 +      EOF
 +      SETUP_END
 +
 +       tree=$(git rev-parse --verify N10:) &&
 +       git fast-import <<-INPUT_END &&
 +      commit refs/heads/N11
 +      committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +      data <<COMMIT
 +      copy to root by id and modify
 +      COMMIT
 +
 +      M 040000 $tree ""
 +      M 100644 inline foo/bar/qux
 +      data <<EOF
 +      hello, world
 +      EOF
 +      R "foo" ""
 +      C "bar/qux" "bar/quux"
 +      INPUT_END
 +       git show N11:bar/baz >actual.baz &&
 +       git show N11:bar/qux >actual.qux &&
 +       git show N11:bar/quux >actual.quux &&
 +       test_cmp expect.baz actual.baz &&
 +       test_cmp expect.qux actual.qux &&
 +       test_cmp expect.qux actual.quux'
 +
  ###
  ### series O
  ###
@@@ -1764,253 -1653,6 +1806,253 @@@ test_expect_success 'R: feature no-rela
      test_cmp marks.new non-relative.out
  '
  
 +test_expect_success 'R: feature cat-blob supported' '
 +      echo "feature cat-blob" |
 +      git fast-import
 +'
 +
 +test_expect_success 'R: cat-blob-fd must be a nonnegative integer' '
 +      test_must_fail git fast-import --cat-blob-fd=-1 </dev/null
 +'
 +
 +test_expect_success 'R: print old blob' '
 +      blob=$(echo "yes it can" | git hash-object -w --stdin) &&
 +      cat >expect <<-EOF &&
 +      ${blob} blob 11
 +      yes it can
 +
 +      EOF
 +      echo "cat-blob $blob" |
 +      git fast-import --cat-blob-fd=6 6>actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'R: in-stream cat-blob-fd not respected' '
 +      echo hello >greeting &&
 +      blob=$(git hash-object -w greeting) &&
 +      cat >expect <<-EOF &&
 +      ${blob} blob 6
 +      hello
 +
 +      EOF
 +      git fast-import --cat-blob-fd=3 3>actual.3 >actual.1 <<-EOF &&
 +      cat-blob $blob
 +      EOF
 +      test_cmp expect actual.3 &&
 +      test_cmp empty actual.1 &&
 +      git fast-import 3>actual.3 >actual.1 <<-EOF &&
 +      option cat-blob-fd=3
 +      cat-blob $blob
 +      EOF
 +      test_cmp empty actual.3 &&
 +      test_cmp expect actual.1
 +'
 +
 +test_expect_success 'R: print new blob' '
 +      blob=$(echo "yep yep yep" | git hash-object --stdin) &&
 +      cat >expect <<-EOF &&
 +      ${blob} blob 12
 +      yep yep yep
 +
 +      EOF
 +      git fast-import --cat-blob-fd=6 6>actual <<-\EOF &&
 +      blob
 +      mark :1
 +      data <<BLOB_END
 +      yep yep yep
 +      BLOB_END
 +      cat-blob :1
 +      EOF
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'R: print new blob by sha1' '
 +      blob=$(echo "a new blob named by sha1" | git hash-object --stdin) &&
 +      cat >expect <<-EOF &&
 +      ${blob} blob 25
 +      a new blob named by sha1
 +
 +      EOF
 +      git fast-import --cat-blob-fd=6 6>actual <<-EOF &&
 +      blob
 +      data <<BLOB_END
 +      a new blob named by sha1
 +      BLOB_END
 +      cat-blob $blob
 +      EOF
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'setup: big file' '
 +      (
 +              echo "the quick brown fox jumps over the lazy dog" >big &&
 +              for i in 1 2 3
 +              do
 +                      cat big big big big >bigger &&
 +                      cat bigger bigger bigger bigger >big ||
 +                      exit
 +              done
 +      )
 +'
 +
 +test_expect_success 'R: print two blobs to stdout' '
 +      blob1=$(git hash-object big) &&
 +      blob1_len=$(wc -c <big) &&
 +      blob2=$(echo hello | git hash-object --stdin) &&
 +      {
 +              echo ${blob1} blob $blob1_len &&
 +              cat big &&
 +              cat <<-EOF
 +
 +              ${blob2} blob 6
 +              hello
 +
 +              EOF
 +      } >expect &&
 +      {
 +              cat <<-\END_PART1 &&
 +                      blob
 +                      mark :1
 +                      data <<data_end
 +              END_PART1
 +              cat big &&
 +              cat <<-\EOF
 +                      data_end
 +                      blob
 +                      mark :2
 +                      data <<data_end
 +                      hello
 +                      data_end
 +                      cat-blob :1
 +                      cat-blob :2
 +              EOF
 +      } |
 +      git fast-import >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'setup: have pipes?' '
 +      rm -f frob &&
 +      if mkfifo frob
 +      then
 +              test_set_prereq PIPE
 +      fi
 +'
 +
 +test_expect_success PIPE 'R: copy using cat-file' '
 +      expect_id=$(git hash-object big) &&
 +      expect_len=$(wc -c <big) &&
 +      echo $expect_id blob $expect_len >expect.response &&
 +
 +      rm -f blobs &&
 +      cat >frontend <<-\FRONTEND_END &&
 +      #!/bin/sh
 +      FRONTEND_END
 +
 +      mkfifo blobs &&
 +      (
 +              export GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE &&
 +              cat <<-\EOF &&
 +              feature cat-blob
 +              blob
 +              mark :1
 +              data <<BLOB
 +              EOF
 +              cat big &&
 +              cat <<-\EOF &&
 +              BLOB
 +              cat-blob :1
 +              EOF
 +
 +              read blob_id type size <&3 &&
 +              echo "$blob_id $type $size" >response &&
 +              head_c $size >blob <&3 &&
 +              read newline <&3 &&
 +
 +              cat <<-EOF &&
 +              commit refs/heads/copied
 +              committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +              data <<COMMIT
 +              copy big file as file3
 +              COMMIT
 +              M 644 inline file3
 +              data <<BLOB
 +              EOF
 +              cat blob &&
 +              echo BLOB
 +      ) 3<blobs |
 +      git fast-import --cat-blob-fd=3 3>blobs &&
 +      git show copied:file3 >actual &&
 +      test_cmp expect.response response &&
 +      test_cmp big actual
 +'
 +
 +test_expect_success PIPE 'R: print blob mid-commit' '
 +      rm -f blobs &&
 +      echo "A blob from _before_ the commit." >expect &&
 +      mkfifo blobs &&
 +      (
 +              exec 3<blobs &&
 +              cat <<-EOF &&
 +              feature cat-blob
 +              blob
 +              mark :1
 +              data <<BLOB
 +              A blob from _before_ the commit.
 +              BLOB
 +              commit refs/heads/temporary
 +              committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +              data <<COMMIT
 +              Empty commit
 +              COMMIT
 +              cat-blob :1
 +              EOF
 +
 +              read blob_id type size <&3 &&
 +              head_c $size >actual <&3 &&
 +              read newline <&3 &&
 +
 +              echo
 +      ) |
 +      git fast-import --cat-blob-fd=3 3>blobs &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success PIPE 'R: print staged blob within commit' '
 +      rm -f blobs &&
 +      echo "A blob from _within_ the commit." >expect &&
 +      mkfifo blobs &&
 +      (
 +              exec 3<blobs &&
 +              cat <<-EOF &&
 +              feature cat-blob
 +              commit refs/heads/within
 +              committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 +              data <<COMMIT
 +              Empty commit
 +              COMMIT
 +              M 644 inline within
 +              data <<BLOB
 +              A blob from _within_ the commit.
 +              BLOB
 +              EOF
 +
 +              to_get=$(
 +                      echo "A blob from _within_ the commit." |
 +                      git hash-object --stdin
 +              ) &&
 +              echo "cat-blob $to_get" &&
 +
 +              read blob_id type size <&3 &&
 +              head_c $size >actual <&3 &&
 +              read newline <&3 &&
 +
 +              echo deleteall
 +      ) |
 +      git fast-import --cat-blob-fd=3 3>blobs &&
 +      test_cmp expect actual
 +'
 +
  cat >input << EOF
  option git quiet
  blob
  
  EOF
  
 -touch empty
 -
  test_expect_success 'R: quiet option results in no stats being output' '
      cat input | git fast-import 2> output &&
      test_cmp empty output
@@@ -2036,14 -1680,6 +2078,14 @@@ test_expect_success 'R: unknown command
      test_must_fail git fast-import --non-existing-option < /dev/null
  '
  
 +test_expect_success 'R: die on invalid option argument' '
 +      echo "option git active-branches=-5" |
 +      test_must_fail git fast-import &&
 +      echo "option git depth=" |
 +      test_must_fail git fast-import &&
 +      test_must_fail git fast-import --depth="5 elephants" </dev/null
 +'
 +
  cat >input <<EOF
  option non-existing-vcs non-existing-option
  EOF
diff --combined t/test-lib.sh
index 42f2f144969a0671b3431f21f659beaff2c53b3d,7ffd7d36ade0520438fda3e2a1d39716b6f594d3..0fdc541a7cd7d6b694c4f4a3fd06b6cf21dc7380
@@@ -70,6 -70,9 +70,9 @@@ unset GIT_NOTES_RE
  unset GIT_NOTES_DISPLAY_REF
  unset GIT_NOTES_REWRITE_REF
  unset GIT_NOTES_REWRITE_MODE
+ unset GIT_REFLOG_ACTION
+ unset GIT_CHERRY_PICK_HELP
+ unset GIT_QUIET
  GIT_MERGE_VERBOSITY=5
  export GIT_MERGE_VERBOSITY
  export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
@@@ -305,17 -308,6 +308,17 @@@ remove_cr () 
        tr '\015' Q | sed -e 's/Q$//'
  }
  
 +# In some bourne shell implementations, the "unset" builtin returns
 +# nonzero status when a variable to be unset was not set in the first
 +# place.
 +#
 +# Use sane_unset when that should not be considered an error.
 +
 +sane_unset () {
 +      unset "$@"
 +      return 0
 +}
 +
  test_tick () {
        if test -z "${test_tick+set}"
        then
@@@ -410,15 -402,6 +413,15 @@@ test_have_prereq () 
        test $total_prereq = $ok_prereq
  }
  
 +test_declared_prereq () {
 +      case ",$test_prereq," in
 +      *,$1,*)
 +              return 0
 +              ;;
 +      esac
 +      return 1
 +}
 +
  # You are not expected to call test_ok_ and test_failure_ directly, use
  # the text_expect_* functions instead.
  
@@@ -471,17 -454,17 +474,17 @@@ test_skip () 
                        break
                esac
        done
 -      if test -z "$to_skip" && test -n "$prereq" &&
 -         ! test_have_prereq "$prereq"
 +      if test -z "$to_skip" && test -n "$test_prereq" &&
 +         ! test_have_prereq "$test_prereq"
        then
                to_skip=t
        fi
        case "$to_skip" in
        t)
                of_prereq=
 -              if test "$missing_prereq" != "$prereq"
 +              if test "$missing_prereq" != "$test_prereq"
                then
 -                      of_prereq=" of $prereq"
 +                      of_prereq=" of $test_prereq"
                fi
  
                say_color skip >&3 "skipping test: $@"
  }
  
  test_expect_failure () {
 -      test "$#" = 3 && { prereq=$1; shift; } || prereq=
 +      test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
        test "$#" = 2 ||
        error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
 +      export test_prereq
        if ! test_skip "$@"
        then
                say >&3 "checking known breakage: $2"
  }
  
  test_expect_success () {
 -      test "$#" = 3 && { prereq=$1; shift; } || prereq=
 +      test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
        test "$#" = 2 ||
        error "bug in the test script: not 2 or 3 parameters to test-expect-success"
 +      export test_prereq
        if ! test_skip "$@"
        then
                say >&3 "expecting success: $2"
        echo >&3 ""
  }
  
 -test_expect_code () {
 -      test "$#" = 4 && { prereq=$1; shift; } || prereq=
 -      test "$#" = 3 ||
 -      error "bug in the test script: not 3 or 4 parameters to test-expect-code"
 -      if ! test_skip "$@"
 -      then
 -              say >&3 "expecting exit code $1: $3"
 -              test_run_ "$3"
 -              if [ "$?" = 0 -a "$eval_ret" = "$1" ]
 -              then
 -                      test_ok_ "$2"
 -              else
 -                      test_failure_ "$@"
 -              fi
 -      fi
 -      echo >&3 ""
 -}
 -
  # test_external runs external test scripts that provide continuous
  # test output about their progress, and succeeds/fails on
  # zero/non-zero exit code.  It outputs the test output on stdout even
  # Usage: test_external description command arguments...
  # Example: test_external 'Perl API' perl ../path/to/test.pl
  test_external () {
 -      test "$#" = 4 && { prereq=$1; shift; } || prereq=
 +      test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq=
        test "$#" = 3 ||
        error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
        descr="$1"
        shift
 +      export test_prereq
        if ! test_skip "$descr" "$@"
        then
                # Announce the script to reduce confusion about the
@@@ -647,28 -645,6 +650,28 @@@ test_path_is_missing () 
        fi
  }
  
 +# test_line_count checks that a file has the number of lines it
 +# ought to. For example:
 +#
 +#     test_expect_success 'produce exactly one line of output' '
 +#             do something >output &&
 +#             test_line_count = 1 output
 +#     '
 +#
 +# is like "test $(wc -l <output) = 1" except that it passes the
 +# output through when the number of lines is wrong.
 +
 +test_line_count () {
 +      if test $# != 3
 +      then
 +              error "bug in the test script: not 3 parameters to test_line_count"
 +      elif ! test $(wc -l <"$3") "$1" "$2"
 +      then
 +              echo "test_line_count: line count for $3 !$1 $2"
 +              cat "$3"
 +              return 1
 +      fi
 +}
  
  # This is not among top-level (test_expect_success | test_expect_failure)
  # but is a prefix that can be used in the test script, like:
@@@ -722,28 -698,6 +725,28 @@@ test_might_fail () 
        return 0
  }
  
 +# Similar to test_must_fail and test_might_fail, but check that a
 +# given command exited with a given exit code. Meant to be used as:
 +#
 +#     test_expect_success 'Merge with d/f conflicts' '
 +#             test_expect_code 1 git merge "merge msg" B master
 +#     '
 +
 +test_expect_code () {
 +      want_code=$1
 +      shift
 +      "$@"
 +      exit_code=$?
 +      if test $exit_code = $want_code
 +      then
 +              echo >&2 "test_expect_code: command exited with $exit_code: $*"
 +              return 0
 +      else
 +              echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
 +              return 1
 +      fi
 +}
 +
  # test_cmp is a helper function to compare actual and expected output.
  # You can use it like:
  #
@@@ -1056,20 -1010,11 +1059,20 @@@ case $(uname -s) i
        # no POSIX permissions
        # backslashes in pathspec are converted to '/'
        # exec does not inherit the PID
 +      test_set_prereq MINGW
 +      test_set_prereq SED_STRIPS_CR
 +      ;;
 +*CYGWIN*)
 +      test_set_prereq POSIXPERM
 +      test_set_prereq EXECKEEPSPID
 +      test_set_prereq NOT_MINGW
 +      test_set_prereq SED_STRIPS_CR
        ;;
  *)
        test_set_prereq POSIXPERM
        test_set_prereq BSLASHPSPEC
        test_set_prereq EXECKEEPSPID
 +      test_set_prereq NOT_MINGW
        ;;
  esac