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

Commit

Permalink
Merge pull request #172 from frogonwheels/mrg/symlink-v6
Browse files Browse the repository at this point in the history
Symlink support
  • Loading branch information
dscho committed Jun 24, 2014
2 parents 7e872d2 + 3e5df5d commit 840d1f9
Show file tree
Hide file tree
Showing 28 changed files with 939 additions and 92 deletions.
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

# 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

0 comments on commit 840d1f9

Please sign in to comment.