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
}