Browse Source

Add support for GIT_CEILING_DIRECTORIES

Make git recognize a new environment variable that prevents it from
chdir'ing up into specified directories when looking for a GIT_DIR.
Useful for avoiding slow network directories.

For example, I use git in an environment where homedirs are automounted
and "ls /home/nonexistent" takes about 9 seconds.  Setting
GIT_CEILING_DIRS="/home" allows "git help -a" (for bash completion) and
"git symbolic-ref" (for my shell prompt) to run in a reasonable time.

Signed-off-by: David Reiss <dreiss@facebook.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
maint
David Reiss 17 years ago committed by Junio C Hamano
parent
commit
0454dd93bf
  1. 8
      Documentation/git.txt
  2. 2
      cache.h
  3. 43
      path.c
  4. 24
      setup.c
  5. 47
      t/t0060-path-utils.sh
  6. 163
      t/t1504-ceiling-dirs.sh
  7. 1
      t/test-lib.sh
  8. 5
      test-path-utils.c

8
Documentation/git.txt

@ -415,6 +415,14 @@ git so take care if using Cogito etc. @@ -415,6 +415,14 @@ git so take care if using Cogito etc.
This can also be controlled by the '--work-tree' command line
option and the core.worktree configuration variable.

'GIT_CEILING_DIRECTORIES'::
This should be a colon-separated list of absolute paths.
If set, it is a list of directories that git should not chdir
up into while looking for a repository directory.
It will not exclude the current working directory or
a GIT_DIR set on the command line or in the environment.
(Useful for excluding slow-loading network directories.)

git Commits
~~~~~~~~~~~
'GIT_AUTHOR_NAME'::

2
cache.h

@ -300,6 +300,7 @@ static inline enum object_type object_type(unsigned int mode) @@ -300,6 +300,7 @@ static inline enum object_type object_type(unsigned int mode)
#define CONFIG_ENVIRONMENT "GIT_CONFIG"
#define CONFIG_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL"
#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
#define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
#define GITATTRIBUTES_FILE ".gitattributes"
#define INFOATTRIBUTES_FILE "info/attributes"
#define ATTRIBUTE_MACRO_PREFIX "[attr]"
@ -515,6 +516,7 @@ static inline int is_absolute_path(const char *path) @@ -515,6 +516,7 @@ static inline int is_absolute_path(const char *path)
}
const char *make_absolute_path(const char *path);
int normalize_absolute_path(char *buf, const char *path);
int longest_ancestor_length(const char *path, const char *prefix_list);

/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
extern int sha1_object_info(const unsigned char *, unsigned long *);

43
path.c

@ -410,3 +410,46 @@ int normalize_absolute_path(char *buf, const char *path) @@ -410,3 +410,46 @@ int normalize_absolute_path(char *buf, const char *path)
*dst = '\0';
return dst - buf;
}

/*
* path = Canonical absolute path
* prefix_list = Colon-separated list of absolute paths
*
* Determines, for each path in parent_list, whether the "prefix" really
* is an ancestor directory of path. Returns the length of the longest
* ancestor directory, excluding any trailing slashes, or -1 if no prefix
* is an ancestor. (Note that this means 0 is returned if prefix_list is
* "/".) "/foo" is not considered an ancestor of "/foobar". Directories
* are not considered to be their own ancestors. path must be in a
* canonical form: empty components, or "." or ".." components are not
* allowed. prefix_list may be null, which is like "".
*/
int longest_ancestor_length(const char *path, const char *prefix_list)
{
char buf[PATH_MAX+1];
const char *ceil, *colon;
int len, max_len = -1;

if (prefix_list == NULL || !strcmp(path, "/"))
return -1;

for (colon = ceil = prefix_list; *colon; ceil = colon+1) {
for (colon = ceil; *colon && *colon != ':'; colon++);
len = colon - ceil;
if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil))
continue;
strlcpy(buf, ceil, len+1);
len = normalize_absolute_path(buf, buf);
/* Strip "trailing slashes" from "/". */
if (len == 1)
len = 0;

if (!strncmp(path, buf, len) &&
path[len] == '/' &&
len > max_len) {
max_len = len;
}
}

return max_len;
}

24
setup.c

@ -359,10 +359,11 @@ const char *read_gitfile_gently(const char *path) @@ -359,10 +359,11 @@ const char *read_gitfile_gently(const char *path)
const char *setup_git_directory_gently(int *nongit_ok)
{
const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
static char cwd[PATH_MAX+1];
const char *gitdirenv;
const char *gitfile_dir;
int len, offset;
int len, offset, ceil_offset;

/*
* Let's assume that we are in a git repository.
@ -414,6 +415,8 @@ const char *setup_git_directory_gently(int *nongit_ok) @@ -414,6 +415,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
if (!getcwd(cwd, sizeof(cwd)-1))
die("Unable to read current working directory");

ceil_offset = longest_ancestor_length(cwd, env_ceiling_dirs);

/*
* Test in the following order (relative to the cwd):
* - .git (file containing "gitdir: <path>")
@ -444,17 +447,16 @@ const char *setup_git_directory_gently(int *nongit_ok) @@ -444,17 +447,16 @@ const char *setup_git_directory_gently(int *nongit_ok)
return NULL;
}
chdir("..");
do {
if (!offset) {
if (nongit_ok) {
if (chdir(cwd))
die("Cannot come back to cwd");
*nongit_ok = 1;
return NULL;
}
die("Not a git repository");
while (--offset > ceil_offset && cwd[offset] != '/');
if (offset <= ceil_offset) {
if (nongit_ok) {
if (chdir(cwd))
die("Cannot come back to cwd");
*nongit_ok = 1;
return NULL;
}
} while (cwd[--offset] != '/');
die("Not a git repository");
}
}

inside_git_dir = 0;

47
t/t0060-path-utils.sh

@ -12,6 +12,11 @@ norm_abs() { @@ -12,6 +12,11 @@ norm_abs() {
"test \$(test-path-utils normalize_absolute_path '$1') = '$2'"
}

ancestor() {
test_expect_success "longest ancestor" \
"test \$(test-path-utils longest_ancestor_length '$1' '$2') = '$3'"
}

norm_abs "" /
norm_abs / /
norm_abs // /
@ -37,4 +42,46 @@ norm_abs /d1/s1//../s2/../../d2 /d2 @@ -37,4 +42,46 @@ norm_abs /d1/s1//../s2/../../d2 /d2
norm_abs /d1/.../d2 /d1/.../d2
norm_abs /d1/..././../d2 /d1/d2

ancestor / "" -1
ancestor / / -1
ancestor /foo "" -1
ancestor /foo : -1
ancestor /foo ::. -1
ancestor /foo ::..:: -1
ancestor /foo / 0
ancestor /foo /fo -1
ancestor /foo /foo -1
ancestor /foo /foo/ -1
ancestor /foo /bar -1
ancestor /foo /bar/ -1
ancestor /foo /foo/bar -1
ancestor /foo /foo:/bar/ -1
ancestor /foo /foo/:/bar/ -1
ancestor /foo /foo::/bar/ -1
ancestor /foo /:/foo:/bar/ 0
ancestor /foo /foo:/:/bar/ 0
ancestor /foo /:/bar/:/foo 0
ancestor /foo/bar "" -1
ancestor /foo/bar / 0
ancestor /foo/bar /fo -1
ancestor /foo/bar foo -1
ancestor /foo/bar /foo 4
ancestor /foo/bar /foo/ 4
ancestor /foo/bar /foo/ba -1
ancestor /foo/bar /:/fo 0
ancestor /foo/bar /foo:/foo/ba 4
ancestor /foo/bar /bar -1
ancestor /foo/bar /bar/ -1
ancestor /foo/bar /fo: -1
ancestor /foo/bar :/fo -1
ancestor /foo/bar /foo:/bar/ 4
ancestor /foo/bar /:/foo:/bar/ 4
ancestor /foo/bar /foo:/:/bar/ 4
ancestor /foo/bar /:/bar/:/fo 0
ancestor /foo/bar /:/bar/ 0
ancestor /foo/bar :://foo/. 4
ancestor /foo/bar :://foo/.:: 4
ancestor /foo/bar //foo/./::/bar 4
ancestor /foo/bar ::/bar -1

test_done

163
t/t1504-ceiling-dirs.sh

@ -0,0 +1,163 @@ @@ -0,0 +1,163 @@
#!/bin/sh

test_description='test GIT_CEILING_DIRECTORIES'
. ./test-lib.sh

test_prefix() {
test_expect_success "$1" \
"test '$2' = \"\$(git rev-parse --show-prefix)\""
}

test_fail() {
test_expect_code 128 "$1: prefix" \
"git rev-parse --show-prefix"
}

TRASH_ROOT="$(pwd)"
ROOT_PARENT=$(dirname "$TRASH_ROOT")


unset GIT_CEILING_DIRECTORIES
test_prefix no_ceil ""

export GIT_CEILING_DIRECTORIES

GIT_CEILING_DIRECTORIES=""
test_prefix ceil_empty ""

GIT_CEILING_DIRECTORIES="$ROOT_PARENT"
test_prefix ceil_at_parent ""

GIT_CEILING_DIRECTORIES="$ROOT_PARENT/"
test_prefix ceil_at_parent_slash ""

GIT_CEILING_DIRECTORIES="$TRASH_ROOT"
test_prefix ceil_at_trash ""

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/"
test_prefix ceil_at_trash_slash ""

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub"
test_prefix ceil_at_sub ""

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/"
test_prefix ceil_at_sub_slash ""


mkdir -p sub/dir || exit 1
cd sub/dir || exit 1

unset GIT_CEILING_DIRECTORIES
test_prefix subdir_no_ceil "sub/dir/"

export GIT_CEILING_DIRECTORIES

GIT_CEILING_DIRECTORIES=""
test_prefix subdir_ceil_empty "sub/dir/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT"
test_fail subdir_ceil_at_trash

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/"
test_fail subdir_ceil_at_trash_slash

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub"
test_fail subdir_ceil_at_sub

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/"
test_fail subdir_ceil_at_sub_slash

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/dir"
test_prefix subdir_ceil_at_subdir "sub/dir/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/dir/"
test_prefix subdir_ceil_at_subdir_slash "sub/dir/"


GIT_CEILING_DIRECTORIES="$TRASH_ROOT/su"
test_prefix subdir_ceil_at_su "sub/dir/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/su/"
test_prefix subdir_ceil_at_su_slash "sub/dir/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/di"
test_prefix subdir_ceil_at_sub_di "sub/dir/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/di"
test_prefix subdir_ceil_at_sub_di_slash "sub/dir/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/subdi"
test_prefix subdir_ceil_at_subdi "sub/dir/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/subdi"
test_prefix subdir_ceil_at_subdi_slash "sub/dir/"


GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub"
test_fail second_of_two

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:bar"
test_fail first_of_two

GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub:bar"
test_fail second_of_three


GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub"
GIT_DIR=../../.git
export GIT_DIR
test_prefix git_dir_specified ""
unset GIT_DIR


cd ../.. || exit 1
mkdir -p s/d || exit 1
cd s/d || exit 1

unset GIT_CEILING_DIRECTORIES
test_prefix sd_no_ceil "s/d/"

export GIT_CEILING_DIRECTORIES

GIT_CEILING_DIRECTORIES=""
test_prefix sd_ceil_empty "s/d/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT"
test_fail sd_ceil_at_trash

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/"
test_fail sd_ceil_at_trash_slash

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s"
test_fail sd_ceil_at_s

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s/"
test_fail sd_ceil_at_s_slash

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s/d"
test_prefix sd_ceil_at_sd "s/d/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s/d/"
test_prefix sd_ceil_at_sd_slash "s/d/"


GIT_CEILING_DIRECTORIES="$TRASH_ROOT/su"
test_prefix sd_ceil_at_su "s/d/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/su/"
test_prefix sd_ceil_at_su_slash "s/d/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s/di"
test_prefix sd_ceil_at_s_di "s/d/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s/di"
test_prefix sd_ceil_at_s_di_slash "s/d/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sdi"
test_prefix sd_ceil_at_sdi "s/d/"

GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sdi"
test_prefix sd_ceil_at_sdi_slash "s/d/"


test_done

1
t/test-lib.sh

@ -35,6 +35,7 @@ unset GIT_WORK_TREE @@ -35,6 +35,7 @@ unset GIT_WORK_TREE
unset GIT_EXTERNAL_DIFF
unset GIT_INDEX_FILE
unset GIT_OBJECT_DIRECTORY
unset GIT_CEILING_DIRECTORIES
unset SHA1_FILE_DIRECTORIES
unset SHA1_FILE_DIRECTORY
GIT_MERGE_VERBOSITY=5

5
test-path-utils.c

@ -17,5 +17,10 @@ int main(int argc, char **argv) @@ -17,5 +17,10 @@ int main(int argc, char **argv)
}
}

if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) {
int len = longest_ancestor_length(argv[2], argv[3]);
printf("%d\n", len);
}

return 0;
}

Loading…
Cancel
Save