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

Symlink support #172

Merged
merged 14 commits into from
Jun 24, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
575 changes: 532 additions & 43 deletions compat/mingw.c

Large diffs are not rendered by default.

14 changes: 10 additions & 4 deletions compat/mingw.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,6 @@ struct itimerval {
* trivial stubs
*/

static inline int readlink(const char *path, char *buf, size_t bufsiz)
{ errno = ENOSYS; return -1; }
static inline int symlink(const char *oldpath, const char *newpath)
{ errno = ENOSYS; return -1; }
static inline int fchmod(int fildes, mode_t mode)
{ errno = ENOSYS; return -1; }
static inline pid_t fork(void)
Expand Down Expand Up @@ -148,6 +144,10 @@ static inline int mingw_SSL_set_wfd(SSL *ssl, int fd)
#define SSL_set_wfd mingw_SSL_set_wfd
#endif

#undef symlink_with_type
#define symlink_with_type(a,b,c) mingw_symlink((a),(b),(c))
#define symlink(a,b) mingw_symlink((a),(b),GIT_TARGET_UNKNOWN)

/*
* implementations of missing functions
*/
Expand All @@ -164,6 +164,9 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out);
int sigaction(int sig, struct sigaction *in, struct sigaction *out);
int link(const char *oldpath, const char *newpath);

int mingw_symlink(const char *oldpath, const char *newpath, enum git_target_type targettype);
int readlink(const char *path, char *buf, size_t bufsiz);

/*
* replacements of existing functions
*/
Expand Down Expand Up @@ -376,6 +379,9 @@ void mingw_open_html(const char *path);
void mingw_mark_as_git_dir(const char *dir);
#define mark_as_git_dir mingw_mark_as_git_dir

char *mingw_resolve_symlink(char *p, size_t s);
#define resolve_symlink mingw_resolve_symlink

/**
* Max length of long paths (exceeding MAX_PATH). The actual maximum supported
* by NTFS is 32,767 (* sizeof(wchar_t)), but we choose an arbitrary smaller
Expand Down
9 changes: 8 additions & 1 deletion compat/win32/dirent.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
xwcstoutf(ent->d_name, fdata->cFileName, MAX_PATH * 3);

/* Set file type, based on WIN32_FIND_DATA */
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
/* First check for symlinks since a directory symlink has the FILE_ATTRIBUTE_DIRECTORY
* attribute as well. Posix doesn't distinguish between directory/file symlinks, but
* NTFS does.
*/
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT
&& (fdata->dwReserved0 == IO_REPARSE_TAG_SYMLINK))
ent->d_type = DT_LNK;
else if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
ent->d_type = DT_DIR;
else
ent->d_type = DT_REG;
Expand Down
48 changes: 48 additions & 0 deletions contrib/workdir/git-new-workdir
Original file line number Diff line number Diff line change
@@ -1,5 +1,52 @@
#!/bin/sh
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git-new-workdir was always meant to use the plain .git file feature, i.e. writing a file called .git with the single line gitdir: <path>. Given all the problems with reparse points, I am very reluctant to go the route outlined in this diff.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we have an msys with functional ln and test commands wrt symlinks, then this can go away. Meanwhile, this is required for the tests to work. git-new-workdir didn't funciton for me without these changes. Is there a rewrite of git-new-workdir somewhere that works without symlinks? If so I'm quite happy to use that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git-new-workdir is in contrib/, so upstream does not even test it on a regular basis. I would rather not care about it too much in this context, because the (long overdue) rewrite to use the gitfile feature is completely independent of the issue of using reparse points.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OH, I forgot to mention - git-new-workdir is used by the tests, which is why I need it to work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind my comment about using gitdir:... it does not work in this case because we cannot share the complete .git/ directory; parts need to be different (and lead to tremendous problems, which is why git new-workdir never became part of the official Git).


# Fix some commands on Windows
case $(uname -s) in
*MINGW*)
winpath () {
# pwd -W is the only way of getting msys to convert the path, especially mounted paths like /usr
# since cmd /c only takes a single parameter preventing msys automatic path conversion.
if test "${1:~-1:1}" != "/" ; then
echo "$1" | sed 's+/+\\+g'
elif test -d "$1" ; then
(cd "$1"; pwd -W) | sed 's+/+\\+g'
elif test -d "${1%/*}" ; then
(cd "${1%/*}"; echo "$(pwd -W)/${1##*/}") | sed 's+/+\\+g'
else
echo "$1" | sed -e 's+^/\([a-z]\)/+\1:/+' -e 's+/+\\+g'
fi
}
# git sees Windows-style pwd
pwd () {
builtin pwd -W
}
# use mklink
ln () {

ln_sym_hard=/H
ln_sym_dir=
if test "$1" = "-s"
then
ln_sym_hard=
shift
fi
pushd $(dirname "$2") 2>&1 > /dev/null
builtin test -d "$1" && ln_sym_dir=/D
popd > /dev/null 2> /dev/null
cmd /c "mklink ${ln_sym_hard}${ln_sym_dir} \"$(winpath "$2")\" \"$(winpath "$1")\">/dev/null " 2>/dev/null
}

test () {
case "$1" in
-h)
test_file=$(cmd /c "@dir /b/a:l \"$(winpath "${2}")\" 2> nul" )
builtin test -n "${test_file}"
;;
*) builtin test "$@";;
esac
}
esac

usage () {
echo "usage:" $@
exit 127
Expand Down Expand Up @@ -70,6 +117,7 @@ do
mkdir -p "$(dirname "$new_workdir/.git/$x")"
;;
esac
test -e "$git_dir/$x" || mkdir "$git_dir/$x"
ln -s "$git_dir/$x" "$new_workdir/.git/$x"
done

Expand Down
125 changes: 124 additions & 1 deletion entry.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,126 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path,
return result;
}

/*
* Does 'match' match the given name?
* A match is found if
*
* (1) the 'match' string is leading directory of 'name', or
* (2) the 'match' string is exactly the same as 'name'.
*
* and the return value tells which case it was.
*
* It returns 0 when there is no match.
*
* Preserved and simplified from dir.c for use here (without glob special matching)
*/
static int match_one(const char *match, const char *name, int namelen)
{
int matchlen;

/* If the match was just the prefix, we matched */
if (!*match)
return MATCHED_RECURSIVELY;

if (ignore_case) {
for (;;) {
unsigned char c1 = tolower(*match);
unsigned char c2 = tolower(*name);
if (c1 == '\0' )
break;
if (c1 != c2)
return 0;
match++;
name++;
namelen--;
}
/* We don't match the matchstring exactly, */
matchlen = strlen(match);
if (strncmp_icase(match, name, matchlen))
return 0;
} else {
for (;;) {
unsigned char c1 = *match;
unsigned char c2 = *name;
if (c1 == '\0' )
break;
if (c1 != c2)
return 0;
match++;
name++;
namelen--;
}
/* We don't match the matchstring exactly, */
matchlen = strlen(match);
if (strncmp(match, name, matchlen))
return 0;
}

if (namelen == matchlen)
return MATCHED_EXACTLY;
if (match[matchlen-1] == '/' || name[matchlen] == '/')
return MATCHED_RECURSIVELY;
return 0;
}

static enum git_target_type get_symlink_type(const char *filepath, const char *symlinkpath)
{
/* For certain O/S and file-systems, symlinks need to know before-hand whether it
* is a directory or a file being pointed to.
*
* This allows us to use index information for relative paths that lie
* within the working directory.
*
* This function is not interested in interrogating the file-system.
*/
char *sanitized;
const char *fpos, *last;
enum git_target_type ret;
int len, pos;

/* This is an absolute path, so git doesn't know.
*/
if (is_absolute_path(symlinkpath))
return GIT_TARGET_UNKNOWN;

/* Work on a sanitized version of the path that can be
* matched against the index.
*/
last = NULL;
for (fpos = filepath; *fpos; ++fpos)
if (is_dir_sep(*fpos))
last = fpos;

if (last) {
len = (1+last-filepath);
sanitized = xmalloc(len + strlen(symlinkpath)+1);
memcpy(sanitized, filepath, 1+last-filepath);
} else {
len = 0;
sanitized = xmalloc(strlen(symlinkpath)+1);
}
strcpy(sanitized+len, symlinkpath);

ret = GIT_TARGET_UNKNOWN;
if (!normalize_path_copy(sanitized, sanitized)) {
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
switch (match_one(sanitized, ce->name, ce_namelen(ce))) {
case MATCHED_EXACTLY:
case MATCHED_FNMATCH:
ret = GIT_TARGET_ISFILE;
break;
case MATCHED_RECURSIVELY:
ret = GIT_TARGET_ISDIR;
break;
}
}
}

free(sanitized);
return ret;
}

static int write_entry(struct cache_entry *ce,
char *path, const struct checkout *state, int to_tempfile)
{
Expand Down Expand Up @@ -165,7 +285,10 @@ static int write_entry(struct cache_entry *ce,
path, sha1_to_hex(ce->sha1));

if (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) {
ret = symlink(new, path);
/* Note that symlink_with_type is a macro, and that for filesystems that
* don't care, get_symlink_type will not be called.
*/
ret = symlink_with_type(new, path, get_symlink_type(path, new));
free(new);
if (ret)
return error("unable to create symlink %s (%s)",
Expand Down
10 changes: 10 additions & 0 deletions git-compat-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@
#define _NETBSD_SOURCE 1
#define _SGI_SOURCE 1

/* default is not to pass type - mingw needs this */
#define symlink_with_type(a,b,c) symlink((a),(b))

/* Used for 'Target Type' Parameter for symlink_with_type */
enum git_target_type {
GIT_TARGET_UNKNOWN,
GIT_TARGET_ISFILE,
GIT_TARGET_ISDIR
};

#if defined(WIN32) && !defined(__CYGWIN__) /* Both MinGW and MSVC */
# if defined (_MSC_VER) && !defined(_WIN32_WINNT)
# define _WIN32_WINNT 0x0502
Expand Down
6 changes: 6 additions & 0 deletions lockfile.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ static void remove_lock_file_on_signal(int signo)
raise(signo);
}

/* mingw requires its own version of resolve_symlink to be use,
* including in lock_file below
*/
#ifndef resolve_symlink

/*
* p = absolute or relative path name
*
Expand Down Expand Up @@ -121,6 +126,7 @@ static char *resolve_symlink(char *p, size_t s)
return p;
}

#endif

static int lock_file(struct lock_file *lk, const char *path, int flags)
{
Expand Down
14 changes: 13 additions & 1 deletion t/t0000-basic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,22 @@ test_expect_success 'adding various types of objects with git update-index --add
for p in $paths
do
echo "hello $p" >$p || exit 1
# Create files for msys
path=${p%/*}/
if [ "${path}" == "${p}/" ] ; then
path=
fi
linkfile="${path}hello $p"
linkpath="${linkfile%/*}"
if [ "${linkpath}" != "${linkfile}" ] ; then
mkdir -p "${linkpath}"
fi
touch "${linkfile}"

test_ln_s_add "hello $p" ${p}sym || exit 1
done
) &&
find path* ! -type d -print | xargs git update-index --add
find path* ! -type d -print | grep -v hello| xargs git update-index --add
'

# Show them and see that matches what we expect.
Expand Down
2 changes: 1 addition & 1 deletion t/t0050-filesystem.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test_expect_success "detection of filesystem w/o symlink support during repo ini
test "$(git config --bool core.symlinks)" = true
'
else
test_expect_success "detection of filesystem w/o symlink support during repo init" '
test_expect_failure "detection of filesystem w/o symlink support during repo init" '
v=$(git config --bool core.symlinks) &&
test "$v" = false
'
Expand Down
2 changes: 1 addition & 1 deletion t/t0060-path-utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ test_expect_success SYMLINKS 'real path works on symlinks' '
mkdir second &&
ln -s ../first second/other &&
mkdir third &&
dir="$(cd .git; pwd -P)" &&
dir="$(abspath_of_dir .git)" &&
dir2=third/../second/other/.git &&
test "$dir" = "$(test-path-utils real_path $dir2)" &&
file="$dir"/index &&
Expand Down
3 changes: 2 additions & 1 deletion t/t1504-ceiling-dirs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ test_prefix ceil_at_sub ""
GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub/"
test_prefix ceil_at_sub_slash ""

mkdir -p sub/dir || exit 1

if test_have_prereq SYMLINKS
then
ln -s sub top
fi

mkdir -p sub/dir || exit 1
cd sub/dir || exit 1

unset GIT_CEILING_DIRECTORIES
Expand Down
3 changes: 2 additions & 1 deletion t/t2201-add-update-typechange.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ test_expect_success setup '
>yomin &&
>caskly &&
if test_have_prereq SYMLINKS; then
touch frotz
ln -s frotz nitfol &&
T_letter=T
else
Expand All @@ -33,13 +34,13 @@ test_expect_success modify '
>nitfol &&
# rezrov/bozbar disappears
rm -fr rezrov &&
mkdir xyzzy &&
if test_have_prereq SYMLINKS; then
ln -s xyzzy rezrov
else
printf %s xyzzy > rezrov
fi &&
# xyzzy disappears (not a submodule)
mkdir xyzzy &&
echo gnusto >xyzzy/bozbar &&
# yomin gets replaced with a submodule
mkdir yomin &&
Expand Down
3 changes: 3 additions & 0 deletions t/t3010-ls-files-killed-modified.sh
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ test_expect_success 'git ls-files -k to show killed files.' '
date >path3 &&
date >path5
fi &&
touch xyzzy
rm path1
rm xyzzy
mkdir -p path0 path1 path6 pathx/ju &&
date >path0/file0 &&
date >path1/file1 &&
Expand Down
Loading