From: Junio C Hamano Date: Sun, 1 Jul 2007 20:10:42 +0000 (-0700) Subject: Merge branch 'ei/worktree+filter' X-Git-Tag: v1.5.3-rc0~30 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/0305b636542c8c137ed7c82fee90db8d3621118c?ds=inline;hp=-c Merge branch 'ei/worktree+filter' * 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 --- 0305b636542c8c137ed7c82fee90db8d3621118c diff --combined Documentation/config.txt index a2057d9d24,4d0bd37af9..3dc17a6d78 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@@ -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 is logged to the file "$GIT_DIR/logs/", 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. - - diff --combined Documentation/git-rev-parse.txt index 87771b832b,6e4d15829d..eea9c9cfe9 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@@ -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 826914837b,4b567d8028..2cc0b214d2 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@@ -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=:: + 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 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 0525c4ee55,ae1990a54e..dcadfef929 --- a/cache.h +++ 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" @@@ -207,6 -208,7 +208,7 @@@ 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 a5afd2a536,aafa416229..65e79edc77 --- a/connect.c +++ 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 8fa5ce6467,614f7bd3c7..a2fcebc1c6 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@@ -119,10 -119,6 +119,10 @@@ # 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 @@@ -178,14 -174,6 +178,14 @@@ # # 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 @@@ -288,9 -280,6 +288,9 @@@ --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 @@@ -372,7 -348,7 +373,7 @@@ 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 @@@ -387,7 -363,7 +388,7 @@@ 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 98687f54d7,886b898fcc..03d5e2d979 --- a/git-svn.perl +++ b/git-svn.perl @@@ -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) @@@ -2005,7 -1990,7 +2004,7 @@@ $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 { @@@ -2937,7 -2922,6 +2936,7 @@@ 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; @@@ -3120,15 -3107,6 +3119,15 @@@ 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 cfec5d70ee,cd3910afea..b6bf5ad5ec --- a/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 }, @@@ -274,9 -244,9 +283,9 @@@ { "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 }, @@@ -304,7 -274,7 +313,7 @@@ { "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 }, @@@ -317,9 -287,9 +326,9 @@@ { "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 }, @@@ -347,13 -317,25 +356,13 @@@ 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, '/'); @@@ -386,7 -368,7 +395,7 @@@ 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); } @@@ -418,7 -400,7 +427,7 @@@ 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 14f62c42e3,dba8012659..01f74d4644 --- a/setup.c +++ 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; @@@ -218,41 -273,92 +273,92 @@@ 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 8bf4cf49a2,b61e1d598d..78d7e87e86 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@@ -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