Skip to content

Commit 7ec30aa

Browse files
mhaggergitster
authored andcommitted
Provide a mechanism to turn off symlink resolution in ceiling paths
Commit 1b77d83 'setup_git_directory_gently_1(): resolve symlinks in ceiling paths' changed the setup code to resolve symlinks in the entries in GIT_CEILING_DIRECTORIES. Because those entries are compared textually to the symlink-resolved current directory, an entry in GIT_CEILING_DIRECTORIES that contained a symlink would have no effect. It was known that this could cause performance problems if the symlink resolution *itself* touched slow filesystems, but it was thought that such use cases would be unlikely. The intention of the earlier change was to deal with a case when the user has this: GIT_CEILING_DIRECTORIES=/home/gitster but in reality, /home/gitster is a symbolic link to somewhere else, e.g. /net/machine/home4/gitster. A textual comparison between the specified value /home/gitster and the location getcwd(3) returns would not help us, but readlink("/home/gitster") would still be fast. After this change was released, Anders Kaseorg <[email protected]> reported: > [...] my computer has been acting so slow when I’m not connected to > the network. I put various network filesystem paths in > $GIT_CEILING_DIRECTORIES, such as > /afs/athena.mit.edu/user/a/n/andersk (to avoid hitting its parents > /afs/athena.mit.edu, /afs/athena.mit.edu/user/a, and > /afs/athena.mit.edu/user/a/n which all live in different AFS > volumes). Now when I’m not connected to the network, every > invocation of Git, including the __git_ps1 in my shell prompt, waits > for AFS to timeout. To allow users to work around this problem, give them a mechanism to turn off symlink resolution in GIT_CEILING_DIRECTORIES entries. All the entries that follow an empty entry will not be checked for symbolic links and used literally in comparison. E.g. with these: GIT_CEILING_DIRECTORIES=:/foo/bar:/xyzzy or GIT_CEILING_DIRECTORIES=/foo/bar::/xyzzy we will not readlink("/xyzzy") because it comes after an empty entry. With the former (but not with the latter), "/foo/bar" comes after an empty entry, and we will not readlink it, either. Signed-off-by: Michael Haggerty <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1b77d83 commit 7ec30aa

File tree

3 files changed

+52
-16
lines changed

3 files changed

+52
-16
lines changed

Documentation/git.txt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -653,12 +653,19 @@ git so take care if using Cogito etc.
653653
The '--namespace' command-line option also sets this value.
654654

655655
'GIT_CEILING_DIRECTORIES'::
656-
This should be a colon-separated list of absolute paths.
657-
If set, it is a list of directories that git should not chdir
658-
up into while looking for a repository directory.
659-
It will not exclude the current working directory or
660-
a GIT_DIR set on the command line or in the environment.
661-
(Useful for excluding slow-loading network directories.)
656+
This should be a colon-separated list of absolute paths. If
657+
set, it is a list of directories that git should not chdir up
658+
into while looking for a repository directory (useful for
659+
excluding slow-loading network directories). It will not
660+
exclude the current working directory or a GIT_DIR set on the
661+
command line or in the environment. Normally, Git has to read
662+
the entries in this list and resolve any symlink that
663+
might be present in order to compare them with the current
664+
directory. However, if even this access is slow, you
665+
can add an empty entry to the list to tell Git that the
666+
subsequent entries are not symlinks and needn't be resolved;
667+
e.g.,
668+
'GIT_CEILING_DIRECTORIES=/maybe/symlink::/very/slow/non/symlink'.
662669

663670
'GIT_DISCOVERY_ACROSS_FILESYSTEM'::
664671
When run in a directory that does not have ".git" repository

setup.c

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -624,22 +624,32 @@ static dev_t get_device_or_die(const char *path, const char *prefix, int prefix_
624624
/*
625625
* A "string_list_each_func_t" function that canonicalizes an entry
626626
* from GIT_CEILING_DIRECTORIES using real_path_if_valid(), or
627-
* discards it if unusable.
627+
* discards it if unusable. The presence of an empty entry in
628+
* GIT_CEILING_DIRECTORIES turns off canonicalization for all
629+
* subsequent entries.
628630
*/
629631
static int canonicalize_ceiling_entry(struct string_list_item *item,
630-
void *unused)
632+
void *cb_data)
631633
{
634+
int *empty_entry_found = cb_data;
632635
char *ceil = item->string;
633-
const char *real_path;
634636

635-
if (!*ceil || !is_absolute_path(ceil))
637+
if (!*ceil) {
638+
*empty_entry_found = 1;
636639
return 0;
637-
real_path = real_path_if_valid(ceil);
638-
if (!real_path)
640+
} else if (!is_absolute_path(ceil)) {
639641
return 0;
640-
free(item->string);
641-
item->string = xstrdup(real_path);
642-
return 1;
642+
} else if (*empty_entry_found) {
643+
/* Keep entry but do not canonicalize it */
644+
return 1;
645+
} else {
646+
const char *real_path = real_path_if_valid(ceil);
647+
if (!real_path)
648+
return 0;
649+
free(item->string);
650+
item->string = xstrdup(real_path);
651+
return 1;
652+
}
643653
}
644654

645655
/*
@@ -679,9 +689,11 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
679689
return setup_explicit_git_dir(gitdirenv, cwd, len, nongit_ok);
680690

681691
if (env_ceiling_dirs) {
692+
int empty_entry_found = 0;
693+
682694
string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1);
683695
filter_string_list(&ceiling_dirs, 0,
684-
canonicalize_ceiling_entry, NULL);
696+
canonicalize_ceiling_entry, &empty_entry_found);
685697
ceil_offset = longest_ancestor_length(cwd, &ceiling_dirs);
686698
string_list_clear(&ceiling_dirs, 0);
687699
}

t/t1504-ceiling-dirs.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ test_prefix ceil_at_sub ""
4444
GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/"
4545
test_prefix ceil_at_sub_slash ""
4646

47+
if test_have_prereq SYMLINKS
48+
then
49+
ln -s sub top
50+
fi
4751

4852
mkdir -p sub/dir || exit 1
4953
cd sub/dir || exit 1
@@ -68,6 +72,19 @@ test_fail subdir_ceil_at_sub
6872
GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/"
6973
test_fail subdir_ceil_at_sub_slash
7074

75+
if test_have_prereq SYMLINKS
76+
then
77+
GIT_CEILING_DIRECTORIES="$TRASH_ROOT/top"
78+
test_fail subdir_ceil_at_top
79+
GIT_CEILING_DIRECTORIES="$TRASH_ROOT/top/"
80+
test_fail subdir_ceil_at_top_slash
81+
82+
GIT_CEILING_DIRECTORIES=":$TRASH_ROOT/top"
83+
test_prefix subdir_ceil_at_top_no_resolve "sub/dir/"
84+
GIT_CEILING_DIRECTORIES=":$TRASH_ROOT/top/"
85+
test_prefix subdir_ceil_at_top_slash_no_resolve "sub/dir/"
86+
fi
87+
7188
GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/dir"
7289
test_prefix subdir_ceil_at_subdir "sub/dir/"
7390

0 commit comments

Comments
 (0)