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
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
transfer.unpackLimit::
When `fetch.unpackLimit` or `receive.unpackLimit` are
not set, the value of this variable is used instead.
-
-
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
GIT
---
Part of the gitlink:git[7] suite
-
--------
[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
-----------
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]
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`.
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'::
'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
GIT
---
Part of the gitlink:git[7] suite
-
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))
}
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);
};
#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);
#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);
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);
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);
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);
}
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;
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;
}
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);
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);
# 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
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=
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
;;
/*)
;;
*)
- 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
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
'')
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);
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 ",
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);
}
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);
# 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;
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";
}
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";
}
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(
$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;
#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)
{
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);
* 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);
if (len) {
int speclen = strlen(path);
char *n = xmalloc(speclen + len + 1);
-
+
memcpy(n, prefix, len);
memcpy(n + len, path, speclen+1);
path = n;
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.
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 */
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)
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
mv .git/hooks .git/hooks-disabled
cd "$owd"
}
-
+
test_done () {
trap - exit
case "$test_failure" in