Skip to content
Permalink
Browse files

Merge pull request #2268 from drizzd/dont-clean-junctions

Avoid traversing NTFS junction points in `git clean -dfx`
  • Loading branch information...
dscho committed Jul 23, 2019
2 parents 271c090 + b7ca314 commit 32a748d5c6b0989c0b2b6e7c82f728c554fa5c38
Showing with 153 additions and 2 deletions.
  1. +29 −0 builtin/clean.c
  2. +1 −0 cache.h
  3. +24 −0 compat/mingw.c
  4. +5 −0 compat/mingw.h
  5. +40 −2 compat/win32/fscache.c
  6. +1 −0 compat/win32/fscache.h
  7. +4 −0 git-compat-util.h
  8. +39 −0 path.c
  9. +10 −0 t/t7300-clean.sh
@@ -33,6 +33,10 @@ static const char *msg_remove = N_("Removing %s\n");
static const char *msg_would_remove = N_("Would remove %s\n");
static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
#ifndef CAN_UNLINK_MOUNT_POINTS
static const char *msg_skip_mount_point = N_("Skipping mount point %s\n");
static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n");
#endif
static const char *msg_warn_remove_failed = N_("failed to remove %s");

enum color_clean {
@@ -168,6 +172,29 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
goto out;
}

if (is_mount_point(path)) {
#ifndef CAN_UNLINK_MOUNT_POINTS
if (!quiet) {
quote_path_relative(path->buf, prefix, &quoted);
printf(dry_run ?
_(msg_would_skip_mount_point) :
_(msg_skip_mount_point), quoted.buf);
}
*dir_gone = 0;
#else
if (!dry_run && unlink(path->buf)) {
int saved_errno = errno;
quote_path_relative(path->buf, prefix, &quoted);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
ret = -1;
}
#endif

goto out;
}

dir = opendir(path->buf);
if (!dir) {
/* an empty dir could be removed even if it is unreadble */
@@ -957,6 +984,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)

if (read_cache() < 0)
die(_("index file corrupt"));
enable_fscache(active_nr);

if (!ignored)
setup_standard_excludes(&dir);
@@ -1046,6 +1074,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
strbuf_reset(&abs_path);
}

disable_fscache();
strbuf_release(&abs_path);
strbuf_release(&buf);
string_list_clear(&del_list, 0);
@@ -1267,6 +1267,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
int normalize_path_copy(char *dst, const char *src);
int longest_ancestor_length(const char *path, struct string_list *prefixes);
char *strip_path_suffix(const char *path, const char *suffix);
int is_mount_point_via_stat(struct strbuf *path);
int daemon_avoid_alias(const char *path);

/*
@@ -2845,6 +2845,30 @@ pid_t waitpid(pid_t pid, int *status, int options)
return -1;
}

int (*win32_is_mount_point)(struct strbuf *path) = mingw_is_mount_point;

int mingw_is_mount_point(struct strbuf *path)
{
WIN32_FIND_DATAW findbuf = { 0 };
HANDLE handle;
wchar_t wfilename[MAX_LONG_PATH];
int wlen = xutftowcs_long_path(wfilename, path->buf);
if (wlen < 0)
die(_("could not get long path for '%s'"), path->buf);

/* remove trailing slash, if any */
if (wlen > 0 && wfilename[wlen - 1] == L'/')
wfilename[--wlen] = L'\0';

handle = FindFirstFileW(wfilename, &findbuf);
if (handle == INVALID_HANDLE_VALUE)
return 0;
FindClose(handle);

return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
(findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT);
}

int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
{
int upos = 0, wpos = 0;
@@ -463,6 +463,11 @@ static inline void convert_slashes(char *path)
if (*path == '\\')
*path = '/';
}
struct strbuf;
int mingw_is_mount_point(struct strbuf *path);
extern int (*win32_is_mount_point)(struct strbuf *path);
#define is_mount_point win32_is_mount_point
#define CAN_UNLINK_MOUNT_POINTS 1
#define PATH_SEP ';'
extern char *mingw_query_user_email(void);
#define query_user_email mingw_query_user_email
@@ -41,6 +41,7 @@ static struct trace_key trace_fscache = TRACE_KEY_INIT(FSCACHE);
struct fsentry {
struct hashmap_entry ent;
mode_t st_mode;
ULONG reparse_tag;
/* Length of name. */
unsigned short len;
/*
@@ -180,6 +181,10 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent

fse = fsentry_alloc(cache, list, buf, len);

fse->reparse_tag =
fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ?
fdata->EaSize : 0;

/*
* On certain Windows versions, host directories mapped into
* Windows Containers ("Volumes", see https://docs.docker.com/storage/volumes/)
@@ -189,8 +194,7 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent
* Let's work around this by detecting that situation and
* telling Git that these are *not* symbolic links.
*/
if (fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
fdata->EaSize == IO_REPARSE_TAG_SYMLINK &&
if (fse->reparse_tag == IO_REPARSE_TAG_SYMLINK &&
sizeof(buf) > (list ? list->len + 1 : 0) + fse->len + 1 &&
is_inside_windows_container()) {
size_t off = 0;
@@ -461,6 +465,7 @@ int fscache_enable(size_t initial_size)
/* redirect opendir and lstat to the fscache implementations */
opendir = fscache_opendir;
lstat = fscache_lstat;
win32_is_mount_point = fscache_is_mount_point;
}
initialized++;
LeaveCriticalSection(&fscache_cs);
@@ -521,6 +526,7 @@ void fscache_disable(void)
/* reset opendir and lstat to the original implementations */
opendir = dirent_opendir;
lstat = mingw_lstat;
win32_is_mount_point = mingw_is_mount_point;
}
LeaveCriticalSection(&fscache_cs);

@@ -588,6 +594,38 @@ int fscache_lstat(const char *filename, struct stat *st)
return 0;
}

/*
* is_mount_point() replacement, uses cache if enabled, otherwise falls
* back to mingw_is_mount_point().
*/
int fscache_is_mount_point(struct strbuf *path)
{
int dirlen, base, len;
struct fsentry key[2], *fse;
struct fscache *cache = fscache_getcache();

if (!cache || !do_fscache_enabled(cache, path->buf))
return mingw_is_mount_point(path);

cache->lstat_requests++;
/* split path into path + name */
len = path->len;
if (len && is_dir_sep(path->buf[len - 1]))
len--;
base = len;
while (base && !is_dir_sep(path->buf[base - 1]))
base--;
dirlen = base ? base - 1 : 0;

/* lookup entry for path + name in cache */
fsentry_init(key, NULL, path->buf, dirlen);
fsentry_init(key + 1, key, path->buf + base, len - base);
fse = fscache_get(cache, key + 1);
if (!fse)
return mingw_is_mount_point(path);
return fse->reparse_tag == IO_REPARSE_TAG_MOUNT_POINT;
}

typedef struct fscache_DIR {
struct DIR base_dir; /* extend base struct DIR */
struct fsentry *pfsentry;
@@ -22,6 +22,7 @@ void fscache_flush(void);

DIR *fscache_opendir(const char *dir);
int fscache_lstat(const char *file_name, struct stat *buf);
int fscache_is_mount_point(struct strbuf *path);

/* opaque fscache structure */
struct fscache;
@@ -415,6 +415,10 @@ static inline int git_create_symlink(struct index_state *index, const char *targ
#define create_symlink git_create_symlink
#endif

#ifndef is_mount_point
#define is_mount_point is_mount_point_via_stat
#endif

#ifndef query_user_email
#define query_user_email() NULL
#endif
39 path.c
@@ -1253,6 +1253,45 @@ char *strip_path_suffix(const char *path, const char *suffix)
return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
}

int is_mount_point_via_stat(struct strbuf *path)
{
size_t len = path->len;
unsigned int current_dev;
struct stat st;

if (!strcmp("/", path->buf))
return 1;

strbuf_addstr(path, "/.");
if (lstat(path->buf, &st)) {
/*
* If we cannot access the current directory, we cannot say
* that it is a bind mount.
*/
strbuf_setlen(path, len);
return 0;
}
current_dev = st.st_dev;

/* Now look at the parent directory */
strbuf_addch(path, '.');
if (lstat(path->buf, &st)) {
/*
* If we cannot access the parent directory, we cannot say
* that it is a bind mount.
*/
strbuf_setlen(path, len);
return 0;
}
strbuf_setlen(path, len);

/*
* If the device ID differs between current and parent directory,
* then it is a bind mount.
*/
return current_dev != st.st_dev;
}

int daemon_avoid_alias(const char *p)
{
int sl, ndot;
@@ -680,4 +680,14 @@ test_expect_success MINGW 'handle clean & core.longpaths = false nicely' '
grep "too long" .git/err
'

test_expect_success MINGW 'clean does not traverse mount points' '
mkdir target &&
>target/dont-clean-me &&
git init with-mountpoint &&
cmd //c "mklink /j with-mountpoint\\mountpoint target" &&
git -C with-mountpoint clean -dfx &&
test_path_is_missing with-mountpoint/mountpoint &&
test_path_is_file target/dont-clean-me
'

test_done

0 comments on commit 32a748d

Please sign in to comment.
You can’t perform that action at this time.