Merge branch 'ei/worktree+filter'
authorJunio C Hamano <gitster@pobox.com>
Sun, 1 Jul 2007 20:10:42 +0000 (13:10 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 1 Jul 2007 20:10:42 +0000 (13:10 -0700)
* ei/worktree+filter:
filter-branch: always export GIT_DIR if it is set
setup_git_directory: fix segfault if repository is found in cwd
test GIT_WORK_TREE
extend rev-parse test for --is-inside-work-tree
Use new semantics of is_bare/inside_git_dir/inside_work_tree
introduce GIT_WORK_TREE to specify the work tree
test git rev-parse
rev-parse: introduce --is-bare-repository
rev-parse: document --is-inside-git-dir

1  2 
Documentation/config.txt
Documentation/git-rev-parse.txt
Documentation/git.txt
builtin-ls-files.c
cache.h
connect.c
git-filter-branch.sh
git-svn.perl
git.c
setup.c
t/test-lib.sh
diff --combined Documentation/config.txt
index a2057d9d24432c6092fa875454f2ebdd6b5dec89,4d0bd37af9c74403b25e1d6bc1ae944791c3e179..3dc17a6d787a542d35d41207b9f00e19779ec943
@@@ -172,6 -172,13 +172,13 @@@ repository that ends in "/.git" is assu
  false), while all other repositories are assumed to be bare (bare
  = true).
  
+ core.worktree::
+       Set the path to the working tree.  The value will not be
+       used in combination with repositories found automatically in
+       a .git directory (i.e. $GIT_DIR is not set).
+       This can be overriden by the GIT_WORK_TREE environment
+       variable and the '--work-tree' command line option.
  core.logAllRefUpdates::
        Updates to a ref <ref> is logged to the file
        "$GIT_DIR/logs/<ref>", by appending the new and old
@@@ -531,7 -538,7 +538,7 @@@ merge.summary:
  merge.tool::
        Controls which merge resolution program is used by
        gitlink:git-mergetool[l].  Valid values are: "kdiff3", "tkdiff",
 -      "meld", "xxdiff", "emerge", "vimdiff", and "opendiff"
 +      "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and "opendiff".
  
  merge.verbosity::
        Controls the amount of output shown by the recursive merge
@@@ -682,3 -689,5 +689,3 @@@ receive.denyNonFastForwards:
  transfer.unpackLimit::
        When `fetch.unpackLimit` or `receive.unpackLimit` are
        not set, the value of this variable is used instead.
 -
 -
index 87771b832bdc71a9b18535f0ab21e68478891e46,6e4d15829d4607b9f5b1bafdbd616643266a2538..eea9c9cfe9ec9320b328c37eefb6c7100edab913
@@@ -90,8 -90,15 +90,15 @@@ OPTION
        Show `$GIT_DIR` if defined else show the path to the .git directory.
  
  --is-inside-git-dir::
-       Return "true" if we are in the git directory, otherwise "false".
-       Some commands require to be run in a working directory.
+       When the current working directory is below the repository
+       directory print "true", otherwise "false".
+ --is-inside-work-tree::
+       When the current working directory is inside the work tree of the
+       repository print "true", otherwise "false".
+ --is-bare-repository::
+       When the repository is bare print "true", otherwise "false".
  
  --short, --short=number::
        Instead of outputting the full SHA1 values of object names try to
@@@ -290,3 -297,4 +297,3 @@@ Documentation by Junio C Hamano and th
  GIT
  ---
  Part of the gitlink:git[7] suite
 -
diff --combined Documentation/git.txt
index 826914837bc0a3e1fa5f044f69b837abdd42cf8c,4b567d8028ba78c3182e5416394786b5cded6b45..2cc0b214d20e28321c5097e79af741960e30ea19
@@@ -10,7 -10,8 +10,8 @@@ SYNOPSI
  --------
  [verse]
  'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate]
-     [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]
+     [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]
+     [--help] COMMAND [ARGS]
  
  DESCRIPTION
  -----------
@@@ -41,11 -42,9 +42,11 @@@ unreleased) version of git, that is ava
  branch of the `git.git` repository.
  Documentation for older releases are available here:
  
 -* link:v1.5.2/git.html[documentation for release 1.5.2]
 +* link:v1.5.2.2/git.html[documentation for release 1.5.2.2]
  
  * release notes for
 +  link:RelNotes-1.5.2.2.txt[1.5.2.2],
 +  link:RelNotes-1.5.2.1.txt[1.5.2.1],
    link:RelNotes-1.5.2.txt[1.5.2].
  
  * link:v1.5.1.6/git.html[documentation for release 1.5.1.6]
@@@ -103,6 -102,14 +104,14 @@@ OPTION
        Set the path to the repository. This can also be controlled by
        setting the GIT_DIR environment variable.
  
+ --work-tree=<path>::
+       Set the path to the working tree.  The value will not be
+       used in combination with repositories found automatically in
+       a .git directory (i.e. $GIT_DIR is not set).
+       This can also be controlled by setting the GIT_WORK_TREE
+       environment variable and the core.worktree configuration
+       variable.
  --bare::
        Same as --git-dir=`pwd`.
  
@@@ -347,6 -354,13 +356,13 @@@ git so take care if using Cogito etc
        specifies a path to use instead of the default `.git`
        for the base of the repository.
  
+ 'GIT_WORK_TREE'::
+       Set the path to the working tree.  The value will not be
+       used in combination with repositories found automatically in
+       a .git directory (i.e. $GIT_DIR is not set).
+       This can also be controlled by the '--work-tree' command line
+       option and the core.worktree configuration variable.
  git Commits
  ~~~~~~~~~~~
  'GIT_AUTHOR_NAME'::
@@@ -396,16 -410,6 +412,16 @@@ othe
  'GIT_PAGER'::
        This environment variable overrides `$PAGER`.
  
 +'GIT_FLUSH'::
 +      If this environment variable is set to "1", then commands such
 +      as git-blame (in incremental mode), git-rev-list, git-log,
 +      git-whatchanged, etc., will force a flush of the output stream
 +      after each commit-oriented record have been flushed.   If this
 +      variable is set to "0", the output of these commands will be done
 +      using completely buffered I/O.   If this environment variable is
 +      not set, git will choose buffered or record-oriented flushing
 +      based on whether stdout appears to be redirected to a file or not.
 +
  'GIT_TRACE'::
        If this variable is set to "1", "2" or "true" (comparison
        is case insensitive), git will print `trace:` messages on
@@@ -440,3 -444,4 +456,3 @@@ contributors on the git-list <git@vger.
  GIT
  ---
  Part of the gitlink:git[7] suite
 -
diff --combined builtin-ls-files.c
index 5398a41415372b07fa1fdb43c26b5570e9d990b8,48a313516db12dbb74d1f0293d3a55edc0cb3f2c..61577ea13ffdf0143dcb8a13c37ae49c1f8f2018
@@@ -117,7 -117,7 +117,7 @@@ static void show_other_files(struct dir
                if (0 <= pos)
                        continue;       /* exact match */
                pos = -pos - 1;
 -              if (pos < active_nr) { 
 +              if (pos < active_nr) {
                        ce = active_cache[pos];
                        if (ce_namelen(ce) == len &&
                            !memcmp(ce->name, ent->name, len))
@@@ -470,7 -470,7 +470,7 @@@ int cmd_ls_files(int argc, const char *
        }
  
        if (require_work_tree &&
-                       (is_bare_repository() || is_inside_git_dir()))
+                       (!is_inside_work_tree() || is_inside_git_dir()))
                die("This operation must be run in a work tree");
  
        pathspec = get_pathspec(prefix, argv + i);
diff --combined cache.h
index 0525c4ee55a05304a6b2018cc27933baa5510aba,ae1990a54ea9cd0171e4298af42101b374a932a2..dcadfef929bd0638cfcc9ac42bb43f264d0c0b63
+++ b/cache.h
@@@ -192,6 -192,7 +192,7 @@@ enum object_type 
  };
  
  #define GIT_DIR_ENVIRONMENT "GIT_DIR"
+ #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
  #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
  #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
  #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
  extern int is_bare_repository_cfg;
  extern int is_bare_repository(void);
  extern int is_inside_git_dir(void);
+ extern int is_inside_work_tree(void);
  extern const char *get_git_dir(void);
  extern char *get_object_directory(void);
  extern char *get_refs_directory(void);
@@@ -225,24 -227,6 +227,24 @@@ extern void verify_non_filename(const c
  
  #define alloc_nr(x) (((x)+16)*3/2)
  
 +/*
 + * Realloc the buffer pointed at by variable 'x' so that it can hold
 + * at least 'nr' entries; the number of entries currently allocated
 + * is 'alloc', using the standard growing factor alloc_nr() macro.
 + *
 + * DO NOT USE any expression with side-effect for 'x' or 'alloc'.
 + */
 +#define ALLOC_GROW(x, nr, alloc) \
 +      do { \
 +              if ((nr) > alloc) { \
 +                      if (alloc_nr(alloc) < (nr)) \
 +                              alloc = (nr); \
 +                      else \
 +                              alloc = alloc_nr(alloc); \
 +                      x = xrealloc((x), alloc * sizeof(*(x))); \
 +              } \
 +      } while(0)
 +
  /* Initialize and use the cache information */
  extern int read_index(struct index_state *);
  extern int read_index_from(struct index_state *, const char *path);
@@@ -372,6 -356,7 +374,6 @@@ extern int move_temp_to_file(const cha
  
  extern int has_sha1_pack(const unsigned char *sha1, const char **ignore);
  extern int has_sha1_file(const unsigned char *sha1);
 -extern void *map_sha1_file(const unsigned char *sha1, unsigned long *);
  
  extern int has_pack_file(const unsigned char *sha1);
  extern int has_pack_index(const unsigned char *sha1);
@@@ -496,7 -481,7 +498,7 @@@ extern void prepare_packed_git(void)
  extern void reprepare_packed_git(void);
  extern void install_packed_git(struct packed_git *pack);
  
 -extern struct packed_git *find_sha1_pack(const unsigned char *sha1, 
 +extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
                                         struct packed_git *packs);
  
  extern void pack_report(void);
@@@ -532,8 -517,6 +534,8 @@@ extern char git_default_name[MAX_GITNAM
  extern const char *git_commit_encoding;
  extern const char *git_log_output_encoding;
  
 +/* IO helper functions */
 +extern void maybe_flush_or_die(FILE *, const char *);
  extern int copy_fd(int ifd, int ofd);
  extern int read_in_full(int fd, void *buf, size_t count);
  extern int write_in_full(int fd, const void *buf, size_t count);
diff --combined connect.c
index a5afd2a5361d840347eb64a3a73db9d8dcbd1057,aafa416229df08010203d948923c547eefe3e107..65e79edc77101ba4f195d5ce267c712da52b3b0b
+++ b/connect.c
@@@ -224,10 -224,11 +224,10 @@@ static int git_tcp_connect_sock(char *h
                }
                if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
                        saved_errno = errno;
 -                      fprintf(stderr, "%s[%d: %s]: net=%s, errno=%s\n",
 +                      fprintf(stderr, "%s[%d: %s]: errno=%s\n",
                                host,
                                cnt,
                                ai_name(ai),
 -                              hstrerror(h_errno),
                                strerror(saved_errno));
                        close(sockfd);
                        sockfd = -1;
@@@ -314,10 -315,11 +314,10 @@@ static int git_tcp_connect_sock(char *h
  
                if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
                        saved_errno = errno;
 -                      fprintf(stderr, "%s[%d: %s]: net=%s, errno=%s\n",
 +                      fprintf(stderr, "%s[%d: %s]: errno=%s\n",
                                host,
                                cnt,
                                inet_ntoa(*(struct in_addr *)&sa.sin_addr),
 -                              hstrerror(h_errno),
                                strerror(saved_errno));
                        close(sockfd);
                        sockfd = -1;
@@@ -388,7 -390,7 +388,7 @@@ static int git_proxy_command_options(co
                }
                if (0 <= matchlen) {
                        /* core.gitproxy = none for kernel.org */
 -                      if (matchlen == 4 && 
 +                      if (matchlen == 4 &&
                            !memcmp(value, "none", 4))
                                matchlen = 0;
                        git_proxy_command = xmalloc(matchlen + 1);
@@@ -587,6 -589,7 +587,7 @@@ pid_t git_connect(int fd[2], char *url
                        unsetenv(ALTERNATE_DB_ENVIRONMENT);
                        unsetenv(DB_ENVIRONMENT);
                        unsetenv(GIT_DIR_ENVIRONMENT);
+                       unsetenv(GIT_WORK_TREE_ENVIRONMENT);
                        unsetenv(GRAFT_ENVIRONMENT);
                        unsetenv(INDEX_ENVIRONMENT);
                        execlp("sh", "sh", "-c", command, NULL);
diff --combined git-filter-branch.sh
index 8fa5ce6467b12119868a4c3c74c3e3740f6d6b8e,614f7bd3c78ca565984854a8ad8e438a00e5365d..a2fcebc1c63082db03445b65e340286d0c129903
  #     attached, the rewritten tag won't have it. Sorry. (It is by
  #     definition impossible to preserve signatures at any rate, though.)
  #
 +# --subdirectory-filter DIRECTORY:: Only regard the history, as seen by
 +#     the given subdirectory. The result will contain that directory as
 +#     its project root.
 +#
  # EXAMPLE USAGE
  # -------------
  # Suppose you want to remove a file (containing confidential information
  #
  #     git-filter-branch ... new-H C..H --not D
  #     git-filter-branch ... new-H D..H --not C
 +#
 +# To move the whole tree into a subdirectory, or remove it from there:
 +#
 +# git-filter-branch --index-filter \
 +#     'git-ls-files -s | sed "s-\t-&newsubdir/-" |
 +#             GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
 +#                     git-update-index --index-info &&
 +#      mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' directorymoved
  
  # Testsuite: TODO
  
@@@ -234,6 -222,11 +234,6 @@@ set_ident () 
        echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\""
  }
  
 -# list all parent's object names for a given commit
 -get_parents () {
 -      git-rev-list -1 --parents "$1" | sed "s/^[0-9a-f]*//"
 -}
 -
  tempdir=.git-rewrite
  filter_env=
  filter_tree=
@@@ -242,7 -235,6 +242,7 @@@ filter_parent
  filter_msg=cat
  filter_commit='git-commit-tree "$@"'
  filter_tag_name=
 +filter_subdir=
  while case "$#" in 0) usage;; esac
  do
        case "$1" in
        --tag-name-filter)
                filter_tag_name="$OPTARG"
                ;;
 +      --subdirectory-filter)
 +              filter_subdir="$OPTARG"
 +              ;;
        *)
                usage
                ;;
@@@ -312,9 -301,10 +312,10 @@@ case "$GIT_DIR" i
  /*)
        ;;
  *)
-       export GIT_DIR="$(pwd)/../../$GIT_DIR"
+       GIT_DIR="$(pwd)/../../$GIT_DIR"
        ;;
  esac
+ export GIT_DIR GIT_WORK_TREE=.
  
  export GIT_INDEX_FILE="$(pwd)/../index"
  git-read-tree # seed the index file
@@@ -324,31 -314,17 +325,31 @@@ ret=
  
  mkdir ../map # map old->new commit ids for rewriting parents
  
 -git-rev-list --reverse --topo-order --default HEAD "$@" >../revs
 +case "$filter_subdir" in
 +"")
 +      git-rev-list --reverse --topo-order --default HEAD \
 +              --parents "$@"
 +      ;;
 +*)
 +      git-rev-list --reverse --topo-order --default HEAD \
 +              --parents --full-history "$@" -- "$filter_subdir"
 +esac > ../revs
  commits=$(cat ../revs | wc -l | tr -d " ")
  
  test $commits -eq 0 && die "Found nothing to rewrite"
  
  i=0
 -while read commit; do
 +while read commit parents; do
        i=$(($i+1))
        printf "$commit ($i/$commits) "
  
 -      git-read-tree -i -m $commit
 +      case "$filter_subdir" in
 +      "")
 +              git-read-tree -i -m $commit
 +              ;;
 +      *)
 +              git-read-tree -i -m $commit:"$filter_subdir"
 +      esac
  
        export GIT_COMMIT=$commit
        git-cat-file commit "$commit" >../commit
        eval "$filter_index" < /dev/null
  
        parentstr=
 -      for parent in $(get_parents $commit); do
 +      for parent in $parents; do
                for reparent in $(map "$parent"); do
                        parentstr="$parentstr -p $reparent"
                done
                tee ../map/$commit
  done <../revs
  
 -src_head=$(tail -n 1 ../revs)
 +src_head=$(tail -n 1 ../revs | sed -e 's/ .*//')
  target_head=$(head -n 1 ../map/$src_head)
  case "$target_head" in
  '')
diff --combined git-svn.perl
index 98687f54d79fa2f7212a5cdad80332b4231d50df,886b898fccfb37c0473d2d412f97cef6c4a284df..03d5e2d979c48159643256634861162d34a470ce
@@@ -38,16 -38,14 +38,16 @@@ use IPC::Open3
  use Git;
  
  BEGIN {
 -      my $s;
 +      # import functions from Git into our packages, en masse
 +      no strict 'refs';
        foreach (qw/command command_oneline command_noisy command_output_pipe
                    command_input_pipe command_close_pipe/) {
 -              $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ".
 -                    "*Git::SVN::Migration::$_ = ".
 -                    "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; ";
 +              for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher
 +                      Git::SVN::Migration Git::SVN::Log Git::SVN),
 +                      __PACKAGE__) {
 +                      *{"${package}::$_"} = \&{"Git::$_"};
 +              }
        }
 -      eval $s;
  }
  
  my ($SVN);
@@@ -596,8 -594,7 +596,7 @@@ sub post_fetch_checkout 
        my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index";
        return if -f $index;
  
-       chomp(my $bare = `git config --bool --get core.bare`);
-       return if $bare eq 'true';
+       return if command_oneline(qw/rev-parse --is-inside-work-tree/) eq 'false';
        return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true';
        command_noisy(qw/read-tree -m -u -v HEAD HEAD/);
        print STDERR "Checked out HEAD:\n  ",
@@@ -787,12 -784,12 +786,12 @@@ sub read_repo_config 
  
  sub extract_metadata {
        my $id = shift or return (undef, undef, undef);
 -      my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
 +      my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+)
                                                        \s([a-f\d\-]+)$/x);
        if (!defined $rev || !$uuid || !$url) {
                # some of the original repositories I made had
                # identifiers like this:
 -              ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
 +              ($rev, $uuid) = ($id =~/^\s*git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
        }
        return ($url, $rev, $uuid);
  }
@@@ -804,29 -801,20 +803,29 @@@ sub cmt_metadata 
  
  sub working_head_info {
        my ($head, $refs) = @_;
 -      my ($fh, $ctx) = command_output_pipe('rev-list', $head);
 -      while (my $hash = <$fh>) {
 -              chomp($hash);
 -              my ($url, $rev, $uuid) = cmt_metadata($hash);
 +      my ($fh, $ctx) = command_output_pipe('log', $head);
 +      my $hash;
 +      my %max;
 +      while (<$fh>) {
 +              if ( m{^commit ($::sha1)$} ) {
 +                      unshift @$refs, $hash if $hash and $refs;
 +                      $hash = $1;
 +                      next;
 +              }
 +              next unless s{^\s*(git-svn-id:)}{$1};
 +              my ($url, $rev, $uuid) = extract_metadata($_);
                if (defined $url && defined $rev) {
 +                      next if $max{$url} and $max{$url} < $rev;
                        if (my $gs = Git::SVN->find_by_url($url)) {
                                my $c = $gs->rev_db_get($rev);
                                if ($c && $c eq $hash) {
                                        close $fh; # break the pipe
                                        return ($url, $rev, $uuid, $gs);
 +                              } else {
 +                                      $max{$url} ||= $gs->rev_db_max;
                                }
                        }
                }
 -              unshift @$refs, $hash if $refs;
        }
        command_close_pipe($fh, $ctx);
        (undef, undef, undef, undef);
@@@ -857,26 -845,26 +856,26 @@@ BEGIN 
        # some options are read globally, but can be overridden locally
        # per [svn-remote "..."] section.  Command-line options will *NOT*
        # override options set in an [svn-remote "..."] section
 -      my $e;
 -      foreach (qw/follow_parent no_metadata use_svm_props
 -                  use_svnsync_props/) {
 -              my $key = $_;
 +      no strict 'refs';
 +      for my $option (qw/follow_parent no_metadata use_svm_props
 +                         use_svnsync_props/) {
 +              my $key = $option;
                $key =~ tr/_//d;
 -              $e .= "sub $_ {
 -                      my (\$self) = \@_;
 -                      return \$self->{-$_} if exists \$self->{-$_};
 -                      my \$k = \"svn-remote.\$self->{repo_id}\.$key\";
 -                      eval { command_oneline(qw/config --get/, \$k) };
 -                      if (\$@) {
 -                              \$self->{-$_} = \$Git::SVN::_$_;
 +              my $prop = "-$option";
 +              *$option = sub {
 +                      my ($self) = @_;
 +                      return $self->{$prop} if exists $self->{$prop};
 +                      my $k = "svn-remote.$self->{repo_id}.$key";
 +                      eval { command_oneline(qw/config --get/, $k) };
 +                      if ($@) {
 +                              $self->{$prop} = ${"Git::SVN::_$option"};
                        } else {
 -                              my \$v = command_oneline(qw/config --bool/,\$k);
 -                              \$self->{-$_} = \$v eq 'false' ? 0 : 1;
 +                              my $v = command_oneline(qw/config --bool/,$k);
 +                              $self->{$prop} = $v eq 'false' ? 0 : 1;
                        }
 -                      return \$self->{-$_} }\n";
 +                      return $self->{$prop};
 +              }
        }
 -      $e .= "1;\n";
 -      eval $e or die $@;
  }
  
  my %LOCKFILES;
@@@ -1468,7 -1456,7 +1467,7 @@@ sub tmp_config 
        my (@args) = @_;
        my $old_def_config = "$ENV{GIT_DIR}/svn/config";
        my $config = "$ENV{GIT_DIR}/svn/.metadata";
 -      if (-e $old_def_config && ! -e $config) {
 +      if (! -f $config && -f $old_def_config) {
                rename $old_def_config, $config or
                       die "Failed rename $old_def_config => $config: $!\n";
        }
@@@ -1975,19 -1963,16 +1974,19 @@@ sub rebuild 
                return;
        }
        print "Rebuilding $db_path ...\n";
 -      my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname);
 +      my ($log, $ctx) = command_output_pipe("log", $self->refname);
        my $latest;
        my $full_url = $self->full_url;
        remove_username($full_url);
        my $svn_uuid;
 -      while (<$rev_list>) {
 -              chomp;
 -              my $c = $_;
 -              die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o;
 -              my ($url, $rev, $uuid) = ::cmt_metadata($c);
 +      my $c;
 +      while (<$log>) {
 +              if ( m{^commit ($::sha1)$} ) {
 +                      $c = $1;
 +                      next;
 +              }
 +              next unless s{^\s*(git-svn-id:)}{$1};
 +              my ($url, $rev, $uuid) = ::extract_metadata($_);
                remove_username($url);
  
                # ignore merges (from set-tree)
                $self->rev_db_set($rev, $c);
                print "r$rev = $c\n";
        }
 -      command_close_pipe($rev_list, $ctx);
 +      command_close_pipe($log, $ctx);
        print "Done rebuilding $db_path\n";
  }
  
@@@ -2913,17 -2898,17 +2912,17 @@@ my ($can_do_switch, %ignored_err, $RA)
  
  BEGIN {
        # enforce temporary pool usage for some simple functions
 -      my $e;
 -      foreach (qw/rev_proplist get_latest_revnum get_uuid get_repos_root/) {
 -              $e .= "sub $_ {
 -                      my \$self = shift;
 -                      my \$pool = SVN::Pool->new;
 -                      my \@ret = \$self->SUPER::$_(\@_,\$pool);
 -                      \$pool->clear;
 -                      wantarray ? \@ret : \$ret[0]; }\n";
 +      no strict 'refs';
 +      for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root/) {
 +              my $SUPER = "SUPER::$f";
 +              *$f = sub {
 +                      my $self = shift;
 +                      my $pool = SVN::Pool->new;
 +                      my @ret = $self->$SUPER(@_,$pool);
 +                      $pool->clear;
 +                      wantarray ? @ret : $ret[0];
 +              };
        }
 -
 -      eval "$e; 1;" or die $@;
  }
  
  sub new {
            SVN::Client::get_ssl_server_trust_file_provider(),
            SVN::Client::get_simple_prompt_provider(
              \&Git::SVN::Prompt::simple, 2),
 +          SVN::Client::get_ssl_client_cert_file_provider(),
            SVN::Client::get_ssl_client_cert_prompt_provider(
              \&Git::SVN::Prompt::ssl_client_cert, 2),
            SVN::Client::get_ssl_client_cert_pw_prompt_provider(
@@@ -3087,8 -3071,11 +3086,8 @@@ sub gs_do_switch 
        $editor->{git_commit_ok};
  }
  
 -sub gs_fetch_loop_common {
 -      my ($self, $base, $head, $gsv, $globs) = @_;
 -      return if ($base > $head);
 -      my $inc = $_log_window_size;
 -      my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
 +sub longest_common_path {
 +      my ($gsv, $globs) = @_;
        my %common;
        my $common_max = scalar @$gsv;
  
                        last;
                }
        }
 +      $longest_path;
 +}
 +
 +sub gs_fetch_loop_common {
 +      my ($self, $base, $head, $gsv, $globs) = @_;
 +      return if ($base > $head);
 +      my $inc = $_log_window_size;
 +      my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
 +      my $longest_path = longest_common_path($gsv, $globs);
        while (1) {
                my %revs;
                my $err;
diff --combined git.c
index cfec5d70ee42852f3819fe46e99ebc70f089ccc7,cd3910afea0cc0e66e720413b2e03d8f00964ff6..b6bf5ad5ec97699f0a1f4d6c6616c7c9a240d144
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -4,7 -4,7 +4,7 @@@
  #include "quote.h"
  
  const char git_usage_string[] =
-       "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]";
+       "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
  
  static void prepend_to_path(const char *dir, int len)
  {
@@@ -69,6 -69,16 +69,16 @@@ static int handle_options(const char**
                        handled++;
                } else if (!prefixcmp(cmd, "--git-dir=")) {
                        setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
+               } else if (!strcmp(cmd, "--work-tree")) {
+                       if (*argc < 2) {
+                               fprintf(stderr, "No directory given for --work-tree.\n" );
+                               usage(git_usage_string);
+                       }
+                       setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1);
+                       (*argv)++;
+                       (*argc)--;
+               } else if (!prefixcmp(cmd, "--work-tree=")) {
+                       setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
                } else if (!strcmp(cmd, "--bare")) {
                        static char git_dir[PATH_MAX+1];
                        setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);
@@@ -214,57 -224,17 +224,56 @@@ const char git_version_string[] = GIT_V
   * require working tree to be present -- anything uses this needs
   * RUN_SETUP for reading from the configuration file.
   */
- #define NOT_BARE      (1<<2)
+ #define NEED_WORK_TREE        (1<<2)
  
 -static void handle_internal_command(int argc, const char **argv, char **envp)
 +struct cmd_struct {
 +      const char *cmd;
 +      int (*fn)(int, const char **, const char *);
 +      int option;
 +};
 +
 +static int run_command(struct cmd_struct *p, int argc, const char **argv)
 +{
 +      int status;
 +      struct stat st;
 +      const char *prefix;
 +
 +      prefix = NULL;
 +      if (p->option & RUN_SETUP)
 +              prefix = setup_git_directory();
 +      if (p->option & USE_PAGER)
 +              setup_pager();
-       if (p->option & NOT_BARE) {
-               if (is_bare_repository() || is_inside_git_dir())
-                       die("%s must be run in a work tree", p->cmd);
-       }
++      if ((p->option & NEED_WORK_TREE) &&
++          (!is_inside_work_tree() || is_inside_git_dir()))
++              die("%s must be run in a work tree", p->cmd);
 +      trace_argv_printf(argv, argc, "trace: built-in: git");
 +
 +      status = p->fn(argc, argv, prefix);
 +      if (status)
 +              return status;
 +
 +      /* Somebody closed stdout? */
 +      if (fstat(fileno(stdout), &st))
 +              return 0;
 +      /* Ignore write errors for pipes and sockets.. */
 +      if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode))
 +              return 0;
 +
 +      /* Check for ENOSPC and EIO errors.. */
 +      if (fflush(stdout))
 +              die("write failure on standard output: %s", strerror(errno));
 +      if (ferror(stdout))
 +              die("unknown write failure on standard output");
 +      if (fclose(stdout))
 +              die("close failed on standard output: %s", strerror(errno));
 +      return 0;
 +}
 +
 +static void handle_internal_command(int argc, const char **argv)
  {
        const char *cmd = argv[0];
 -      static struct cmd_struct {
 -              const char *cmd;
 -              int (*fn)(int, const char **, const char *);
 -              int option;
 -      } commands[] = {
 +      static struct cmd_struct commands[] = {
-               { "add", cmd_add, RUN_SETUP | NOT_BARE },
+               { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
                { "annotate", cmd_annotate, RUN_SETUP | USE_PAGER },
                { "apply", cmd_apply },
                { "archive", cmd_archive },
                { "cat-file", cmd_cat_file, RUN_SETUP },
                { "checkout-index", cmd_checkout_index, RUN_SETUP },
                { "check-ref-format", cmd_check_ref_format },
-               { "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE },
+               { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
                { "cherry", cmd_cherry, RUN_SETUP },
-               { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
+               { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
                { "config", cmd_config },
                { "count-objects", cmd_count_objects, RUN_SETUP },
                { "mailsplit", cmd_mailsplit },
                { "merge-base", cmd_merge_base, RUN_SETUP },
                { "merge-file", cmd_merge_file },
-               { "mv", cmd_mv, RUN_SETUP | NOT_BARE },
+               { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
                { "name-rev", cmd_name_rev, RUN_SETUP },
                { "pack-objects", cmd_pack_objects, RUN_SETUP },
                { "pickaxe", cmd_blame, RUN_SETUP | USE_PAGER },
                { "rerere", cmd_rerere, RUN_SETUP },
                { "rev-list", cmd_rev_list, RUN_SETUP },
                { "rev-parse", cmd_rev_parse, RUN_SETUP },
-               { "revert", cmd_revert, RUN_SETUP | NOT_BARE },
-               { "rm", cmd_rm, RUN_SETUP | NOT_BARE },
-               { "runstatus", cmd_runstatus, RUN_SETUP | NOT_BARE },
+               { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
+               { "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
+               { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
                { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
                { "show-branch", cmd_show_branch, RUN_SETUP },
                { "show", cmd_show, RUN_SETUP | USE_PAGER },
  
        for (i = 0; i < ARRAY_SIZE(commands); i++) {
                struct cmd_struct *p = commands+i;
 -              const char *prefix;
                if (strcmp(p->cmd, cmd))
                        continue;
 -
 -              prefix = NULL;
 -              if (p->option & RUN_SETUP)
 -                      prefix = setup_git_directory();
 -              if (p->option & USE_PAGER)
 -                      setup_pager();
 -              if ((p->option & NEED_WORK_TREE) &&
 -                              (!is_inside_work_tree() || is_inside_git_dir()))
 -                      die("%s must be run in a work tree", cmd);
 -              trace_argv_printf(argv, argc, "trace: built-in: git");
 -
 -              exit(p->fn(argc, argv, prefix));
 +              exit(run_command(p, argc, argv));
        }
  }
  
 -int main(int argc, const char **argv, char **envp)
 +int main(int argc, const char **argv)
  {
        const char *cmd = argv[0] ? argv[0] : "git-help";
        char *slash = strrchr(cmd, '/');
        if (!prefixcmp(cmd, "git-")) {
                cmd += 4;
                argv[0] = cmd;
 -              handle_internal_command(argc, argv, envp);
 +              handle_internal_command(argc, argv);
                die("cannot handle %s internally", cmd);
        }
  
  
        while (1) {
                /* See if it's an internal command */
 -              handle_internal_command(argc, argv, envp);
 +              handle_internal_command(argc, argv);
  
                /* .. then try the external ones */
                execv_git_cmd(argv);
diff --combined setup.c
index 14f62c42e3ac6ead75d4963ee705b9a110da3de0,dba8012659cf85fa96cad4fcbc55e377c7e876b9..01f74d4644c35862b2498ee8534213b2d76bf721
+++ b/setup.c
@@@ -39,7 -39,7 +39,7 @@@ const char *prefix_path(const char *pre
        if (len) {
                int speclen = strlen(path);
                char *n = xmalloc(speclen + len + 1);
 -      
 +
                memcpy(n, prefix, len);
                memcpy(n + len, path, speclen+1);
                path = n;
@@@ -47,7 -47,7 +47,7 @@@
        return path;
  }
  
 -/* 
 +/*
   * Unlike prefix_path, this should be used if the named file does
   * not have to interact with index entry; i.e. name of a random file
   * on the filesystem.
@@@ -95,7 -95,7 +95,7 @@@ void verify_non_filename(const char *pr
        const char *name;
        struct stat st;
  
-       if (is_inside_git_dir())
+       if (!is_inside_work_tree() || is_inside_git_dir())
                return;
        if (*arg == '-')
                return; /* flag */
@@@ -174,41 -174,96 +174,96 @@@ static int inside_git_dir = -1
  
  int is_inside_git_dir(void)
  {
-       if (inside_git_dir < 0) {
-               char buffer[1024];
-               if (is_bare_repository())
-                       return (inside_git_dir = 1);
-               if (getcwd(buffer, sizeof(buffer))) {
-                       const char *git_dir = get_git_dir(), *cwd = buffer;
-                       while (*git_dir && *git_dir == *cwd) {
-                               git_dir++;
-                               cwd++;
-                       }
-                       inside_git_dir = !*git_dir;
-               } else
-                       inside_git_dir = 0;
+       if (inside_git_dir >= 0)
+               return inside_git_dir;
+       die("BUG: is_inside_git_dir called before setup_git_directory");
+ }
+ static int inside_work_tree = -1;
+ int is_inside_work_tree(void)
+ {
+       if (inside_git_dir >= 0)
+               return inside_work_tree;
+       die("BUG: is_inside_work_tree called before setup_git_directory");
+ }
+ static char *gitworktree_config;
+ static int git_setup_config(const char *var, const char *value)
+ {
+       if (!strcmp(var, "core.worktree")) {
+               if (gitworktree_config)
+                       strlcpy(gitworktree_config, value, PATH_MAX);
+               return 0;
        }
-       return inside_git_dir;
+       return git_default_config(var, value);
  }
  
  const char *setup_git_directory_gently(int *nongit_ok)
  {
        static char cwd[PATH_MAX+1];
-       const char *gitdirenv;
-       int len, offset;
+       char worktree[PATH_MAX+1], gitdir[PATH_MAX+1];
+       const char *gitdirenv, *gitworktree;
+       int wt_rel_gitdir = 0;
  
-       /*
-        * If GIT_DIR is set explicitly, we're not going
-        * to do any discovery, but we still do repository
-        * validation.
-        */
        gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
-       if (gitdirenv) {
-               if (PATH_MAX - 40 < strlen(gitdirenv))
-                       die("'$%s' too big", GIT_DIR_ENVIRONMENT);
-               if (is_git_directory(gitdirenv))
+       if (!gitdirenv) {
+               int len, offset;
+               if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')
+                       die("Unable to read current working directory");
+               offset = len = strlen(cwd);
+               for (;;) {
+                       if (is_git_directory(".git"))
+                               break;
+                       if (offset == 0) {
+                               offset = -1;
+                               break;
+                       }
+                       chdir("..");
+                       while (cwd[--offset] != '/')
+                               ; /* do nothing */
+               }
+               if (offset >= 0) {
+                       inside_work_tree = 1;
+                       git_config(git_default_config);
+                       if (offset == len) {
+                               inside_git_dir = 0;
+                               return NULL;
+                       }
+                       cwd[len++] = '/';
+                       cwd[len] = '\0';
+                       inside_git_dir = !prefixcmp(cwd + offset + 1, ".git/");
+                       return cwd + offset + 1;
+               }
+               if (chdir(cwd))
+                       die("Cannot come back to cwd");
+               if (!is_git_directory(".")) {
+                       if (nongit_ok) {
+                               *nongit_ok = 1;
+                               return NULL;
+                       }
+                       die("Not a git repository");
+               }
+               setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
+               gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
+               if (!gitdirenv)
+                       die("getenv after setenv failed");
+       }
+       if (PATH_MAX - 40 < strlen(gitdirenv)) {
+               if (nongit_ok) {
+                       *nongit_ok = 1;
                        return NULL;
+               }
+               die("$%s too big", GIT_DIR_ENVIRONMENT);
+       }
+       if (!is_git_directory(gitdirenv)) {
                if (nongit_ok) {
                        *nongit_ok = 1;
                        return NULL;
  
        if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')
                die("Unable to read current working directory");
+       if (chdir(gitdirenv)) {
+               if (nongit_ok) {
+                       *nongit_ok = 1;
+                       return NULL;
+               }
+               die("Cannot change directory to $%s '%s'",
+                       GIT_DIR_ENVIRONMENT, gitdirenv);
+       }
+       if (!getcwd(gitdir, sizeof(gitdir)-1) || gitdir[0] != '/')
+               die("Unable to read current working directory");
+       if (chdir(cwd))
+               die("Cannot come back to cwd");
  
-       offset = len = strlen(cwd);
-       for (;;) {
-               if (is_git_directory(".git"))
-                       break;
-               chdir("..");
-               do {
-                       if (!offset) {
-                               if (is_git_directory(cwd)) {
-                                       if (chdir(cwd))
-                                               die("Cannot come back to cwd");
-                                       setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
-                                       inside_git_dir = 1;
-                                       return NULL;
-                               }
-                               if (nongit_ok) {
-                                       if (chdir(cwd))
-                                               die("Cannot come back to cwd");
-                                       *nongit_ok = 1;
-                                       return NULL;
-                               }
-                               die("Not a git repository");
+       /*
+        * In case there is a work tree we may change the directory,
+        * therefore make GIT_DIR an absolute path.
+        */
+       if (gitdirenv[0] != '/') {
+               setenv(GIT_DIR_ENVIRONMENT, gitdir, 1);
+               gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
+               if (!gitdirenv)
+                       die("getenv after setenv failed");
+               if (PATH_MAX - 40 < strlen(gitdirenv)) {
+                       if (nongit_ok) {
+                               *nongit_ok = 1;
+                               return NULL;
                        }
-               } while (cwd[--offset] != '/');
+                       die("$%s too big after expansion to absolute path",
+                               GIT_DIR_ENVIRONMENT);
+               }
+       }
+       strcat(cwd, "/");
+       strcat(gitdir, "/");
+       inside_git_dir = !prefixcmp(cwd, gitdir);
+       gitworktree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+       if (!gitworktree) {
+               gitworktree_config = worktree;
+               worktree[0] = '\0';
+       }
+       git_config(git_setup_config);
+       if (!gitworktree) {
+               gitworktree_config = NULL;
+               if (worktree[0])
+                       gitworktree = worktree;
+               if (gitworktree && gitworktree[0] != '/')
+                       wt_rel_gitdir = 1;
+       }
+       if (wt_rel_gitdir && chdir(gitdirenv))
+               die("Cannot change directory to $%s '%s'",
+                       GIT_DIR_ENVIRONMENT, gitdirenv);
+       if (gitworktree && chdir(gitworktree)) {
+               if (nongit_ok) {
+                       if (wt_rel_gitdir && chdir(cwd))
+                               die("Cannot come back to cwd");
+                       *nongit_ok = 1;
+                       return NULL;
+               }
+               if (wt_rel_gitdir)
+                       die("Cannot change directory to working tree '%s'"
+                               " from $%s", gitworktree, GIT_DIR_ENVIRONMENT);
+               else
+                       die("Cannot change directory to working tree '%s'",
+                               gitworktree);
        }
+       if (!getcwd(worktree, sizeof(worktree)-1) || worktree[0] != '/')
+               die("Unable to read current working directory");
+       strcat(worktree, "/");
+       inside_work_tree = !prefixcmp(cwd, worktree);
  
-       if (offset == len)
+       if (gitworktree && inside_work_tree && !prefixcmp(worktree, gitdir) &&
+           strcmp(worktree, gitdir)) {
+               inside_git_dir = 0;
+       }
+       if (!inside_work_tree) {
+               if (chdir(cwd))
+                       die("Cannot come back to cwd");
                return NULL;
+       }
  
-       /* Make "offset" point to past the '/', and add a '/' at the end */
-       offset++;
-       cwd[len++] = '/';
-       cwd[len] = 0;
-       inside_git_dir = !prefixcmp(cwd + offset, ".git/");
-       return cwd + offset;
+       if (!strcmp(cwd, worktree))
+               return NULL;
+       return cwd+strlen(worktree);
  }
  
  int git_config_perm(const char *var, const char *value)
diff --combined t/test-lib.sh
index 8bf4cf49a207132d0f517468b6ca1182cca61aeb,b61e1d598de606a496cbc93345600bf8b380f426..78d7e87e86178b90a350714c0b287353a0de20b4
@@@ -26,6 -26,7 +26,7 @@@ GIT_COMMITTER_EMAIL=committer@example.c
  GIT_COMMITTER_NAME='C O Mitter'
  unset GIT_DIFF_OPTS
  unset GIT_DIR
+ unset GIT_WORK_TREE
  unset GIT_EXTERNAL_DIFF
  unset GIT_INDEX_FILE
  unset GIT_OBJECT_DIRECTORY
@@@ -232,7 -233,7 +233,7 @@@ test_create_repo () 
        mv .git/hooks .git/hooks-disabled
        cd "$owd"
  }
 -      
 +
  test_done () {
        trap - exit
        case "$test_failure" in