Skip to content

Commit 08fc060

Browse files
Marcel M. Carygitster
authored andcommitted
git-sh-setup: Fix scripts whose PWD is a symlink into a git work-dir
I want directories of my working tree to be linked to from various paths on my filesystem where third-party components expect them, both in development and production environments. A build system's install step could solve this, but I develop scripts and web pages that don't need to be built. Git's submodule system could solve this, but we tend to develop, branch, and test those directories all in unison, so one big repository feels more natural. We prefer to edit and commit on the symlinked paths, not the canonical ones, and in that setting, "git pull" fails to find the top-level directory of the repository while other commands work fine. "git pull" fails because POSIX shells have a notion of current working directory that is different from getcwd(). The shell stores this path in PWD. As a result, "cd ../" can be interpreted differently in a shell script than chdir("../") in a C program. The shell interprets "../" by essentially stripping the last textual path component from PWD, whereas C chdir() follows the ".." link in the current directory on the filesystem. When PWD is a symlink, these are different destinations. As a result, Git's C commands find the correct top-level working tree, and shell scripts do not. Changes: * When interpreting a relative upward (../) path in cd_to_toplevel, prepend the cwd without symlinks, given by /bin/pwd * Add tests for cd_to_toplevel and "git pull" in a symlinked directory that failed before this fix, plus contrasting scenarios that already worked Signed-off-by: Marcel M. Cary <marcel@oak.homeunix.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 5832d1a commit 08fc060

File tree

3 files changed

+136
-2
lines changed

3 files changed

+136
-2
lines changed

git-sh-setup.sh

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,27 @@ cd_to_toplevel () {
8585
cdup=$(git rev-parse --show-cdup)
8686
if test ! -z "$cdup"
8787
then
88-
cd "$cdup" || {
89-
echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
88+
case "$cdup" in
89+
/*)
90+
# Not quite the same as if we did "cd -P '$cdup'" when
91+
# $cdup contains ".." after symlink path components.
92+
# Don't fix that case at least until Git switches to
93+
# "cd -P" across the board.
94+
phys="$cdup"
95+
;;
96+
..|../*|*/..|*/../*)
97+
# Interpret $cdup relative to the physical, not logical, cwd.
98+
# Probably /bin/pwd is more portable than passing -P to cd or pwd.
99+
phys="$(/bin/pwd)/$cdup"
100+
;;
101+
*)
102+
# There's no "..", so no need to make things absolute.
103+
phys="$cdup"
104+
;;
105+
esac
106+
107+
cd "$phys" || {
108+
echo >&2 "Cannot chdir to $phys, the toplevel of the working tree"
90109
exit 1
91110
}
92111
fi

t/t2300-cd-to-toplevel.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/bin/sh
2+
3+
test_description='cd_to_toplevel'
4+
5+
. ./test-lib.sh
6+
7+
test_cd_to_toplevel () {
8+
test_expect_success "$2" '
9+
(
10+
cd '"'$1'"' &&
11+
. git-sh-setup &&
12+
cd_to_toplevel &&
13+
[ "$(/bin/pwd)" = "$TOPLEVEL" ]
14+
)
15+
'
16+
}
17+
18+
TOPLEVEL="$(/bin/pwd)/repo"
19+
mkdir -p repo/sub/dir
20+
mv .git repo/
21+
SUBDIRECTORY_OK=1
22+
23+
test_cd_to_toplevel repo 'at physical root'
24+
25+
test_cd_to_toplevel repo/sub/dir 'at physical subdir'
26+
27+
ln -s repo symrepo
28+
test_cd_to_toplevel symrepo 'at symbolic root'
29+
30+
ln -s repo/sub/dir subdir-link
31+
test_cd_to_toplevel subdir-link 'at symbolic subdir'
32+
33+
cd repo
34+
ln -s sub/dir internal-link
35+
test_cd_to_toplevel internal-link 'at internal symbolic subdir'
36+
37+
test_done

t/t5521-pull-symlink.sh

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/bin/sh
2+
3+
test_description='pulling from symlinked subdir'
4+
5+
. ./test-lib.sh
6+
7+
# The scenario we are building:
8+
#
9+
# trash\ directory/
10+
# clone-repo/
11+
# subdir/
12+
# bar
13+
# subdir-link -> clone-repo/subdir/
14+
#
15+
# The working directory is subdir-link.
16+
17+
mkdir subdir
18+
echo file >subdir/file
19+
git add subdir/file
20+
git commit -q -m file
21+
git clone -q . clone-repo
22+
ln -s clone-repo/subdir/ subdir-link
23+
24+
25+
# Demonstrate that things work if we just avoid the symlink
26+
#
27+
test_expect_success 'pulling from real subdir' '
28+
(
29+
echo real >subdir/file &&
30+
git commit -m real subdir/file &&
31+
cd clone-repo/subdir/ &&
32+
git pull &&
33+
test real = $(cat file)
34+
)
35+
'
36+
37+
# From subdir-link, pulling should work as it does from
38+
# clone-repo/subdir/.
39+
#
40+
# Instead, the error pull gave was:
41+
#
42+
# fatal: 'origin': unable to chdir or not a git archive
43+
# fatal: The remote end hung up unexpectedly
44+
#
45+
# because git would find the .git/config for the "trash directory"
46+
# repo, not for the clone-repo repo. The "trash directory" repo
47+
# had no entry for origin. Git found the wrong .git because
48+
# git rev-parse --show-cdup printed a path relative to
49+
# clone-repo/subdir/, not subdir-link/. Git rev-parse --show-cdup
50+
# used the correct .git, but when the git pull shell script did
51+
# "cd `git rev-parse --show-cdup`", it ended up in the wrong
52+
# directory. A POSIX shell's "cd" works a little differently
53+
# than chdir() in C; "cd -P" is much closer to chdir().
54+
#
55+
test_expect_success 'pulling from symlinked subdir' '
56+
(
57+
echo link >subdir/file &&
58+
git commit -m link subdir/file &&
59+
cd subdir-link/ &&
60+
git pull &&
61+
test link = $(cat file)
62+
)
63+
'
64+
65+
# Prove that the remote end really is a repo, and other commands
66+
# work fine in this context. It's just that "git pull" breaks.
67+
#
68+
test_expect_success 'pushing from symlinked subdir' '
69+
(
70+
cd subdir-link/ &&
71+
echo push >file &&
72+
git commit -m push ./file &&
73+
git push
74+
) &&
75+
test push = $(git show HEAD:subdir/file)
76+
'
77+
78+
test_done

0 commit comments

Comments
 (0)