@matheusbsbs / matheus-bagliano / commits / 0454dd93bf
Fork 0

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>

David Reiss committed May 19, 2008 at 23:49 UTC 0454dd93bfb2334355ec62fff670d8c6cb3570a1
8 files changed +282 -11
Documentation/git.txt
+8
@@ -415,6 +415,14 @@ git so take care if using Cogito etc.
415 This can also be controlled by the '--work-tree' command line
416 option and the core.worktree configuration variable.
417
418 +'GIT_CEILING_DIRECTORIES'::
419 + This should be a colon-separated list of absolute paths.
420 + If set, it is a list of directories that git should not chdir
421 + up into while looking for a repository directory.
422 + It will not exclude the current working directory or
423 + a GIT_DIR set on the command line or in the environment.
424 + (Useful for excluding slow-loading network directories.)
425 +
426 git Commits
427 ~~~~~~~~~~~
428 'GIT_AUTHOR_NAME'::
cache.h
+2
@@ -300,6 +300,7 @@ static inline enum object_type object_type(unsigned int mode)
300 #define CONFIG_ENVIRONMENT "GIT_CONFIG"
301 #define CONFIG_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL"
302 #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
303 +#define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
304 #define GITATTRIBUTES_FILE ".gitattributes"
305 #define INFOATTRIBUTES_FILE "info/attributes"
306 #define ATTRIBUTE_MACRO_PREFIX "[attr]"
@@ -515,6 +516,7 @@ static inline int is_absolute_path(const char *path)
516 }
517 const char *make_absolute_path(const char *path);
518 int normalize_absolute_path(char *buf, const char *path);
519 +int longest_ancestor_length(const char *path, const char *prefix_list);
520
521 /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
522 extern int sha1_object_info(const unsigned char *, unsigned long *);
path.c
+43
@@ -410,3 +410,46 @@ int normalize_absolute_path(char *buf, const char *path)
410 *dst = '\0';
411 return dst - buf;
412 }
413 +
414 +/*
415 + * path = Canonical absolute path
416 + * prefix_list = Colon-separated list of absolute paths
417 + *
418 + * Determines, for each path in parent_list, whether the "prefix" really
419 + * is an ancestor directory of path. Returns the length of the longest
420 + * ancestor directory, excluding any trailing slashes, or -1 if no prefix
421 + * is an ancestor. (Note that this means 0 is returned if prefix_list is
422 + * "/".) "/foo" is not considered an ancestor of "/foobar". Directories
423 + * are not considered to be their own ancestors. path must be in a
424 + * canonical form: empty components, or "." or ".." components are not
425 + * allowed. prefix_list may be null, which is like "".
426 + */
427 +int longest_ancestor_length(const char *path, const char *prefix_list)
428 +{
429 + char buf[PATH_MAX+1];
430 + const char *ceil, *colon;
431 + int len, max_len = -1;
432 +
433 + if (prefix_list == NULL || !strcmp(path, "/"))
434 + return -1;
435 +
436 + for (colon = ceil = prefix_list; *colon; ceil = colon+1) {
437 + for (colon = ceil; *colon && *colon != ':'; colon++);
438 + len = colon - ceil;
439 + if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil))
440 + continue;
441 + strlcpy(buf, ceil, len+1);
442 + len = normalize_absolute_path(buf, buf);
443 + /* Strip "trailing slashes" from "/". */
444 + if (len == 1)
445 + len = 0;
446 +
447 + if (!strncmp(path, buf, len) &&
448 + path[len] == '/' &&
449 + len > max_len) {
450 + max_len = len;
451 + }
452 + }
453 +
454 + return max_len;
455 +}
setup.c
+13 -11
@@ -359,10 +359,11 @@ const char *read_gitfile_gently(const char *path)
359 const char *setup_git_directory_gently(int *nongit_ok)
360 {
361 const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
362 + const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT);
363 static char cwd[PATH_MAX+1];
364 const char *gitdirenv;
365 const char *gitfile_dir;
365 - int len, offset;
366 + int len, offset, ceil_offset;
367
368 /*
369 * Let's assume that we are in a git repository.
@@ -414,6 +415,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
415 if (!getcwd(cwd, sizeof(cwd)-1))
416 die("Unable to read current working directory");
417
418 + ceil_offset = longest_ancestor_length(cwd, env_ceiling_dirs);
419 +
420 /*
421 * Test in the following order (relative to the cwd):
422 * - .git (file containing "gitdir: <path>")
@@ -444,17 +447,16 @@ const char *setup_git_directory_gently(int *nongit_ok)
447 return NULL;
448 }
449 chdir("..");
447 - do {
448 - if (!offset) {
449 - if (nongit_ok) {
450 - if (chdir(cwd))
451 - die("Cannot come back to cwd");
452 - *nongit_ok = 1;
453 - return NULL;
454 - }
455 - die("Not a git repository");
450 + while (--offset > ceil_offset && cwd[offset] != '/');
451 + if (offset <= ceil_offset) {
452 + if (nongit_ok) {
453 + if (chdir(cwd))
454 + die("Cannot come back to cwd");
455 + *nongit_ok = 1;
456 + return NULL;
457 }
457 - } while (cwd[--offset] != '/');
458 + die("Not a git repository");
459 + }
460 }
461
462 inside_git_dir = 0;
t/t0060-path-utils.sh
+47
@@ -12,6 +12,11 @@ norm_abs() {
12 "test \$(test-path-utils normalize_absolute_path '$1') = '$2'"
13 }
14
15 +ancestor() {
16 + test_expect_success "longest ancestor" \
17 + "test \$(test-path-utils longest_ancestor_length '$1' '$2') = '$3'"
18 +}
19 +
20 norm_abs "" /
21 norm_abs / /
22 norm_abs // /
@@ -37,4 +42,46 @@ norm_abs /d1/s1//../s2/../../d2 /d2
42 norm_abs /d1/.../d2 /d1/.../d2
43 norm_abs /d1/..././../d2 /d1/d2
44
45 +ancestor / "" -1
46 +ancestor / / -1
47 +ancestor /foo "" -1
48 +ancestor /foo : -1
49 +ancestor /foo ::. -1
50 +ancestor /foo ::..:: -1
51 +ancestor /foo / 0
52 +ancestor /foo /fo -1
53 +ancestor /foo /foo -1
54 +ancestor /foo /foo/ -1
55 +ancestor /foo /bar -1
56 +ancestor /foo /bar/ -1
57 +ancestor /foo /foo/bar -1
58 +ancestor /foo /foo:/bar/ -1
59 +ancestor /foo /foo/:/bar/ -1
60 +ancestor /foo /foo::/bar/ -1
61 +ancestor /foo /:/foo:/bar/ 0
62 +ancestor /foo /foo:/:/bar/ 0
63 +ancestor /foo /:/bar/:/foo 0
64 +ancestor /foo/bar "" -1
65 +ancestor /foo/bar / 0
66 +ancestor /foo/bar /fo -1
67 +ancestor /foo/bar foo -1
68 +ancestor /foo/bar /foo 4
69 +ancestor /foo/bar /foo/ 4
70 +ancestor /foo/bar /foo/ba -1
71 +ancestor /foo/bar /:/fo 0
72 +ancestor /foo/bar /foo:/foo/ba 4
73 +ancestor /foo/bar /bar -1
74 +ancestor /foo/bar /bar/ -1
75 +ancestor /foo/bar /fo: -1
76 +ancestor /foo/bar :/fo -1
77 +ancestor /foo/bar /foo:/bar/ 4
78 +ancestor /foo/bar /:/foo:/bar/ 4
79 +ancestor /foo/bar /foo:/:/bar/ 4
80 +ancestor /foo/bar /:/bar/:/fo 0
81 +ancestor /foo/bar /:/bar/ 0
82 +ancestor /foo/bar :://foo/. 4
83 +ancestor /foo/bar :://foo/.:: 4
84 +ancestor /foo/bar //foo/./::/bar 4
85 +ancestor /foo/bar ::/bar -1
86 +
87 test_done
t/t1504-ceiling-dirs.sh new
+163
@@ -0,0 +1,163 @@
1 +#!/bin/sh
2 +
3 +test_description='test GIT_CEILING_DIRECTORIES'
4 +. ./test-lib.sh
5 +
6 +test_prefix() {
7 + test_expect_success "$1" \
8 + "test '$2' = \"\$(git rev-parse --show-prefix)\""
9 +}
10 +
11 +test_fail() {
12 + test_expect_code 128 "$1: prefix" \
13 + "git rev-parse --show-prefix"
14 +}
15 +
16 +TRASH_ROOT="$(pwd)"
17 +ROOT_PARENT=$(dirname "$TRASH_ROOT")
18 +
19 +
20 +unset GIT_CEILING_DIRECTORIES
21 +test_prefix no_ceil ""
22 +
23 +export GIT_CEILING_DIRECTORIES
24 +
25 +GIT_CEILING_DIRECTORIES=""
26 +test_prefix ceil_empty ""
27 +
28 +GIT_CEILING_DIRECTORIES="$ROOT_PARENT"
29 +test_prefix ceil_at_parent ""
30 +
31 +GIT_CEILING_DIRECTORIES="$ROOT_PARENT/"
32 +test_prefix ceil_at_parent_slash ""
33 +
34 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT"
35 +test_prefix ceil_at_trash ""
36 +
37 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/"
38 +test_prefix ceil_at_trash_slash ""
39 +
40 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub"
41 +test_prefix ceil_at_sub ""
42 +
43 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/"
44 +test_prefix ceil_at_sub_slash ""
45 +
46 +
47 +mkdir -p sub/dir || exit 1
48 +cd sub/dir || exit 1
49 +
50 +unset GIT_CEILING_DIRECTORIES
51 +test_prefix subdir_no_ceil "sub/dir/"
52 +
53 +export GIT_CEILING_DIRECTORIES
54 +
55 +GIT_CEILING_DIRECTORIES=""
56 +test_prefix subdir_ceil_empty "sub/dir/"
57 +
58 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT"
59 +test_fail subdir_ceil_at_trash
60 +
61 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/"
62 +test_fail subdir_ceil_at_trash_slash
63 +
64 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub"
65 +test_fail subdir_ceil_at_sub
66 +
67 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/"
68 +test_fail subdir_ceil_at_sub_slash
69 +
70 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/dir"
71 +test_prefix subdir_ceil_at_subdir "sub/dir/"
72 +
73 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/dir/"
74 +test_prefix subdir_ceil_at_subdir_slash "sub/dir/"
75 +
76 +
77 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/su"
78 +test_prefix subdir_ceil_at_su "sub/dir/"
79 +
80 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/su/"
81 +test_prefix subdir_ceil_at_su_slash "sub/dir/"
82 +
83 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/di"
84 +test_prefix subdir_ceil_at_sub_di "sub/dir/"
85 +
86 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/di"
87 +test_prefix subdir_ceil_at_sub_di_slash "sub/dir/"
88 +
89 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/subdi"
90 +test_prefix subdir_ceil_at_subdi "sub/dir/"
91 +
92 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/subdi"
93 +test_prefix subdir_ceil_at_subdi_slash "sub/dir/"
94 +
95 +
96 +GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub"
97 +test_fail second_of_two
98 +
99 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:bar"
100 +test_fail first_of_two
101 +
102 +GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub:bar"
103 +test_fail second_of_three
104 +
105 +
106 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub"
107 +GIT_DIR=../../.git
108 +export GIT_DIR
109 +test_prefix git_dir_specified ""
110 +unset GIT_DIR
111 +
112 +
113 +cd ../.. || exit 1
114 +mkdir -p s/d || exit 1
115 +cd s/d || exit 1
116 +
117 +unset GIT_CEILING_DIRECTORIES
118 +test_prefix sd_no_ceil "s/d/"
119 +
120 +export GIT_CEILING_DIRECTORIES
121 +
122 +GIT_CEILING_DIRECTORIES=""
123 +test_prefix sd_ceil_empty "s/d/"
124 +
125 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT"
126 +test_fail sd_ceil_at_trash
127 +
128 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/"
129 +test_fail sd_ceil_at_trash_slash
130 +
131 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s"
132 +test_fail sd_ceil_at_s
133 +
134 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s/"
135 +test_fail sd_ceil_at_s_slash
136 +
137 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s/d"
138 +test_prefix sd_ceil_at_sd "s/d/"
139 +
140 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s/d/"
141 +test_prefix sd_ceil_at_sd_slash "s/d/"
142 +
143 +
144 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/su"
145 +test_prefix sd_ceil_at_su "s/d/"
146 +
147 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/su/"
148 +test_prefix sd_ceil_at_su_slash "s/d/"
149 +
150 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s/di"
151 +test_prefix sd_ceil_at_s_di "s/d/"
152 +
153 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/s/di"
154 +test_prefix sd_ceil_at_s_di_slash "s/d/"
155 +
156 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sdi"
157 +test_prefix sd_ceil_at_sdi "s/d/"
158 +
159 +GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sdi"
160 +test_prefix sd_ceil_at_sdi_slash "s/d/"
161 +
162 +
163 +test_done
t/test-lib.sh
+1
@@ -35,6 +35,7 @@ unset GIT_WORK_TREE
35 unset GIT_EXTERNAL_DIFF
36 unset GIT_INDEX_FILE
37 unset GIT_OBJECT_DIRECTORY
38 +unset GIT_CEILING_DIRECTORIES
39 unset SHA1_FILE_DIRECTORIES
40 unset SHA1_FILE_DIRECTORY
41 GIT_MERGE_VERBOSITY=5
test-path-utils.c
+5
@@ -17,5 +17,10 @@ int main(int argc, char **argv)
17 }
18 }
19
20 + if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) {
21 + int len = longest_ancestor_length(argv[2], argv[3]);
22 + printf("%d\n", len);
23 + }
24 +
25 return 0;
26 }