Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.

Commit 840d1f9

Browse files
committed
Merge pull request #172 from frogonwheels/mrg/symlink-v6
Symlink support
2 parents 7e872d2 + 3e5df5d commit 840d1f9

28 files changed

+939
-92
lines changed

compat/mingw.c

Lines changed: 532 additions & 43 deletions
Large diffs are not rendered by default.

compat/mingw.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,6 @@ struct itimerval {
8484
* trivial stubs
8585
*/
8686

87-
static inline int readlink(const char *path, char *buf, size_t bufsiz)
88-
{ errno = ENOSYS; return -1; }
89-
static inline int symlink(const char *oldpath, const char *newpath)
90-
{ errno = ENOSYS; return -1; }
9187
static inline int fchmod(int fildes, mode_t mode)
9288
{ errno = ENOSYS; return -1; }
9389
static inline pid_t fork(void)
@@ -148,6 +144,10 @@ static inline int mingw_SSL_set_wfd(SSL *ssl, int fd)
148144
#define SSL_set_wfd mingw_SSL_set_wfd
149145
#endif
150146

147+
#undef symlink_with_type
148+
#define symlink_with_type(a,b,c) mingw_symlink((a),(b),(c))
149+
#define symlink(a,b) mingw_symlink((a),(b),GIT_TARGET_UNKNOWN)
150+
151151
/*
152152
* implementations of missing functions
153153
*/
@@ -164,6 +164,9 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out);
164164
int sigaction(int sig, struct sigaction *in, struct sigaction *out);
165165
int link(const char *oldpath, const char *newpath);
166166

167+
int mingw_symlink(const char *oldpath, const char *newpath, enum git_target_type targettype);
168+
int readlink(const char *path, char *buf, size_t bufsiz);
169+
167170
/*
168171
* replacements of existing functions
169172
*/
@@ -376,6 +379,9 @@ void mingw_open_html(const char *path);
376379
void mingw_mark_as_git_dir(const char *dir);
377380
#define mark_as_git_dir mingw_mark_as_git_dir
378381

382+
char *mingw_resolve_symlink(char *p, size_t s);
383+
#define resolve_symlink mingw_resolve_symlink
384+
379385
/**
380386
* Max length of long paths (exceeding MAX_PATH). The actual maximum supported
381387
* by NTFS is 32,767 (* sizeof(wchar_t)), but we choose an arbitrary smaller

compat/win32/dirent.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
1616
xwcstoutf(ent->d_name, fdata->cFileName, MAX_PATH * 3);
1717

1818
/* Set file type, based on WIN32_FIND_DATA */
19-
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
19+
/* First check for symlinks since a directory symlink has the FILE_ATTRIBUTE_DIRECTORY
20+
* attribute as well. Posix doesn't distinguish between directory/file symlinks, but
21+
* NTFS does.
22+
*/
23+
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT
24+
&& (fdata->dwReserved0 == IO_REPARSE_TAG_SYMLINK))
25+
ent->d_type = DT_LNK;
26+
else if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
2027
ent->d_type = DT_DIR;
2128
else
2229
ent->d_type = DT_REG;

contrib/workdir/git-new-workdir

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,52 @@
11
#!/bin/sh
22

3+
# Fix some commands on Windows
4+
case $(uname -s) in
5+
*MINGW*)
6+
winpath () {
7+
# pwd -W is the only way of getting msys to convert the path, especially mounted paths like /usr
8+
# since cmd /c only takes a single parameter preventing msys automatic path conversion.
9+
if test "${1:~-1:1}" != "/" ; then
10+
echo "$1" | sed 's+/+\\+g'
11+
elif test -d "$1" ; then
12+
(cd "$1"; pwd -W) | sed 's+/+\\+g'
13+
elif test -d "${1%/*}" ; then
14+
(cd "${1%/*}"; echo "$(pwd -W)/${1##*/}") | sed 's+/+\\+g'
15+
else
16+
echo "$1" | sed -e 's+^/\([a-z]\)/+\1:/+' -e 's+/+\\+g'
17+
fi
18+
}
19+
# git sees Windows-style pwd
20+
pwd () {
21+
builtin pwd -W
22+
}
23+
# use mklink
24+
ln () {
25+
26+
ln_sym_hard=/H
27+
ln_sym_dir=
28+
if test "$1" = "-s"
29+
then
30+
ln_sym_hard=
31+
shift
32+
fi
33+
pushd $(dirname "$2") 2>&1 > /dev/null
34+
builtin test -d "$1" && ln_sym_dir=/D
35+
popd > /dev/null 2> /dev/null
36+
cmd /c "mklink ${ln_sym_hard}${ln_sym_dir} \"$(winpath "$2")\" \"$(winpath "$1")\">/dev/null " 2>/dev/null
37+
}
38+
39+
test () {
40+
case "$1" in
41+
-h)
42+
test_file=$(cmd /c "@dir /b/a:l \"$(winpath "${2}")\" 2> nul" )
43+
builtin test -n "${test_file}"
44+
;;
45+
*) builtin test "$@";;
46+
esac
47+
}
48+
esac
49+
350
usage () {
451
echo "usage:" $@
552
exit 127
@@ -70,6 +117,7 @@ do
70117
mkdir -p "$(dirname "$new_workdir/.git/$x")"
71118
;;
72119
esac
120+
test -e "$git_dir/$x" || mkdir "$git_dir/$x"
73121
ln -s "$git_dir/$x" "$new_workdir/.git/$x"
74122
done
75123

entry.c

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,126 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path,
136136
return result;
137137
}
138138

139+
/*
140+
* Does 'match' match the given name?
141+
* A match is found if
142+
*
143+
* (1) the 'match' string is leading directory of 'name', or
144+
* (2) the 'match' string is exactly the same as 'name'.
145+
*
146+
* and the return value tells which case it was.
147+
*
148+
* It returns 0 when there is no match.
149+
*
150+
* Preserved and simplified from dir.c for use here (without glob special matching)
151+
*/
152+
static int match_one(const char *match, const char *name, int namelen)
153+
{
154+
int matchlen;
155+
156+
/* If the match was just the prefix, we matched */
157+
if (!*match)
158+
return MATCHED_RECURSIVELY;
159+
160+
if (ignore_case) {
161+
for (;;) {
162+
unsigned char c1 = tolower(*match);
163+
unsigned char c2 = tolower(*name);
164+
if (c1 == '\0' )
165+
break;
166+
if (c1 != c2)
167+
return 0;
168+
match++;
169+
name++;
170+
namelen--;
171+
}
172+
/* We don't match the matchstring exactly, */
173+
matchlen = strlen(match);
174+
if (strncmp_icase(match, name, matchlen))
175+
return 0;
176+
} else {
177+
for (;;) {
178+
unsigned char c1 = *match;
179+
unsigned char c2 = *name;
180+
if (c1 == '\0' )
181+
break;
182+
if (c1 != c2)
183+
return 0;
184+
match++;
185+
name++;
186+
namelen--;
187+
}
188+
/* We don't match the matchstring exactly, */
189+
matchlen = strlen(match);
190+
if (strncmp(match, name, matchlen))
191+
return 0;
192+
}
193+
194+
if (namelen == matchlen)
195+
return MATCHED_EXACTLY;
196+
if (match[matchlen-1] == '/' || name[matchlen] == '/')
197+
return MATCHED_RECURSIVELY;
198+
return 0;
199+
}
200+
201+
static enum git_target_type get_symlink_type(const char *filepath, const char *symlinkpath)
202+
{
203+
/* For certain O/S and file-systems, symlinks need to know before-hand whether it
204+
* is a directory or a file being pointed to.
205+
*
206+
* This allows us to use index information for relative paths that lie
207+
* within the working directory.
208+
*
209+
* This function is not interested in interrogating the file-system.
210+
*/
211+
char *sanitized;
212+
const char *fpos, *last;
213+
enum git_target_type ret;
214+
int len, pos;
215+
216+
/* This is an absolute path, so git doesn't know.
217+
*/
218+
if (is_absolute_path(symlinkpath))
219+
return GIT_TARGET_UNKNOWN;
220+
221+
/* Work on a sanitized version of the path that can be
222+
* matched against the index.
223+
*/
224+
last = NULL;
225+
for (fpos = filepath; *fpos; ++fpos)
226+
if (is_dir_sep(*fpos))
227+
last = fpos;
228+
229+
if (last) {
230+
len = (1+last-filepath);
231+
sanitized = xmalloc(len + strlen(symlinkpath)+1);
232+
memcpy(sanitized, filepath, 1+last-filepath);
233+
} else {
234+
len = 0;
235+
sanitized = xmalloc(strlen(symlinkpath)+1);
236+
}
237+
strcpy(sanitized+len, symlinkpath);
238+
239+
ret = GIT_TARGET_UNKNOWN;
240+
if (!normalize_path_copy(sanitized, sanitized)) {
241+
for (pos = 0; pos < active_nr; pos++) {
242+
struct cache_entry *ce = active_cache[pos];
243+
switch (match_one(sanitized, ce->name, ce_namelen(ce))) {
244+
case MATCHED_EXACTLY:
245+
case MATCHED_FNMATCH:
246+
ret = GIT_TARGET_ISFILE;
247+
break;
248+
case MATCHED_RECURSIVELY:
249+
ret = GIT_TARGET_ISDIR;
250+
break;
251+
}
252+
}
253+
}
254+
255+
free(sanitized);
256+
return ret;
257+
}
258+
139259
static int write_entry(struct cache_entry *ce,
140260
char *path, const struct checkout *state, int to_tempfile)
141261
{
@@ -165,7 +285,10 @@ static int write_entry(struct cache_entry *ce,
165285
path, sha1_to_hex(ce->sha1));
166286

167287
if (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) {
168-
ret = symlink(new, path);
288+
/* Note that symlink_with_type is a macro, and that for filesystems that
289+
* don't care, get_symlink_type will not be called.
290+
*/
291+
ret = symlink_with_type(new, path, get_symlink_type(path, new));
169292
free(new);
170293
if (ret)
171294
return error("unable to create symlink %s (%s)",

git-compat-util.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@
8585
#define _NETBSD_SOURCE 1
8686
#define _SGI_SOURCE 1
8787

88+
/* default is not to pass type - mingw needs this */
89+
#define symlink_with_type(a,b,c) symlink((a),(b))
90+
91+
/* Used for 'Target Type' Parameter for symlink_with_type */
92+
enum git_target_type {
93+
GIT_TARGET_UNKNOWN,
94+
GIT_TARGET_ISFILE,
95+
GIT_TARGET_ISDIR
96+
};
97+
8898
#if defined(WIN32) && !defined(__CYGWIN__) /* Both MinGW and MSVC */
8999
# if defined (_MSC_VER) && !defined(_WIN32_WINNT)
90100
# define _WIN32_WINNT 0x0502

lockfile.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ static void remove_lock_file_on_signal(int signo)
2929
raise(signo);
3030
}
3131

32+
/* mingw requires its own version of resolve_symlink to be use,
33+
* including in lock_file below
34+
*/
35+
#ifndef resolve_symlink
36+
3237
/*
3338
* p = absolute or relative path name
3439
*
@@ -121,6 +126,7 @@ static char *resolve_symlink(char *p, size_t s)
121126
return p;
122127
}
123128

129+
#endif
124130

125131
static int lock_file(struct lock_file *lk, const char *path, int flags)
126132
{

t/t0000-basic.sh

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,22 @@ test_expect_success 'adding various types of objects with git update-index --add
429429
for p in $paths
430430
do
431431
echo "hello $p" >$p || exit 1
432+
# Create files for msys
433+
path=${p%/*}/
434+
if [ "${path}" == "${p}/" ] ; then
435+
path=
436+
fi
437+
linkfile="${path}hello $p"
438+
linkpath="${linkfile%/*}"
439+
if [ "${linkpath}" != "${linkfile}" ] ; then
440+
mkdir -p "${linkpath}"
441+
fi
442+
touch "${linkfile}"
443+
432444
test_ln_s_add "hello $p" ${p}sym || exit 1
433445
done
434446
) &&
435-
find path* ! -type d -print | xargs git update-index --add
447+
find path* ! -type d -print | grep -v hello| xargs git update-index --add
436448
'
437449

438450
# Show them and see that matches what we expect.

t/t0050-filesystem.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ test_expect_success "detection of filesystem w/o symlink support during repo ini
4545
test "$(git config --bool core.symlinks)" = true
4646
'
4747
else
48-
test_expect_success "detection of filesystem w/o symlink support during repo init" '
48+
test_expect_failure "detection of filesystem w/o symlink support during repo init" '
4949
v=$(git config --bool core.symlinks) &&
5050
test "$v" = false
5151
'

t/t0060-path-utils.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ test_expect_success SYMLINKS 'real path works on symlinks' '
178178
mkdir second &&
179179
ln -s ../first second/other &&
180180
mkdir third &&
181-
dir="$(cd .git; pwd -P)" &&
181+
dir="$(abspath_of_dir .git)" &&
182182
dir2=third/../second/other/.git &&
183183
test "$dir" = "$(test-path-utils real_path $dir2)" &&
184184
file="$dir"/index &&

t/t1504-ceiling-dirs.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,13 @@ test_prefix ceil_at_sub ""
4444
GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/"
4545
test_prefix ceil_at_sub_slash ""
4646

47+
mkdir -p sub/dir || exit 1
48+
4749
if test_have_prereq SYMLINKS
4850
then
4951
ln -s sub top
5052
fi
5153

52-
mkdir -p sub/dir || exit 1
5354
cd sub/dir || exit 1
5455

5556
unset GIT_CEILING_DIRECTORIES

t/t2201-add-update-typechange.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ test_expect_success setup '
1010
>yomin &&
1111
>caskly &&
1212
if test_have_prereq SYMLINKS; then
13+
touch frotz
1314
ln -s frotz nitfol &&
1415
T_letter=T
1516
else
@@ -33,13 +34,13 @@ test_expect_success modify '
3334
>nitfol &&
3435
# rezrov/bozbar disappears
3536
rm -fr rezrov &&
37+
mkdir xyzzy &&
3638
if test_have_prereq SYMLINKS; then
3739
ln -s xyzzy rezrov
3840
else
3941
printf %s xyzzy > rezrov
4042
fi &&
4143
# xyzzy disappears (not a submodule)
42-
mkdir xyzzy &&
4344
echo gnusto >xyzzy/bozbar &&
4445
# yomin gets replaced with a submodule
4546
mkdir yomin &&

t/t3010-ls-files-killed-modified.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ test_expect_success 'git ls-files -k to show killed files.' '
8080
date >path3 &&
8181
date >path5
8282
fi &&
83+
touch xyzzy
84+
rm path1
85+
rm xyzzy
8386
mkdir -p path0 path1 path6 pathx/ju &&
8487
date >path0/file0 &&
8588
date >path1/file1 &&

0 commit comments

Comments
 (0)