зеркало из https://github.com/microsoft/git.git
clean: do not traverse mount points
It seems to be not exactly rare on Windows to install NTFS junction points (the equivalent of "bind mounts" on Linux/Unix) in worktrees, e.g. to map some development tools into a subdirectory. In such a scenario, it is pretty horrible if `git clean -dfx` traverses into the mapped directory and starts to "clean up". Let's just not do that. Let's make sure before we traverse into a directory that it is not a mount point (or junction). This addresses https://github.com/git-for-windows/git/issues/607 Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
Родитель
426327f56d
Коммит
db63be7bb1
|
@ -34,6 +34,8 @@ 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");
|
||||
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");
|
||||
static const char *msg_warn_remove_failed = N_("failed to remove %s");
|
||||
static const char *msg_warn_lstat_failed = N_("could not lstat %s\n");
|
||||
static const char *msg_skip_cwd = N_("Refusing to remove current working directory\n");
|
||||
|
@ -175,6 +177,18 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (is_mount_point(path)) {
|
||||
if (!quiet) {
|
||||
quote_path(path->buf, prefix, "ed, 0);
|
||||
printf(dry_run ?
|
||||
_(msg_would_skip_mount_point) :
|
||||
_(msg_skip_mount_point), quoted.buf);
|
||||
}
|
||||
*dir_gone = 0;
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
dir = opendir(path->buf);
|
||||
if (!dir) {
|
||||
/* an empty dir could be removed even if it is unreadble */
|
||||
|
|
1
cache.h
1
cache.h
|
@ -1313,6 +1313,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);
|
||||
|
||||
/*
|
||||
|
|
|
@ -2506,6 +2506,28 @@ pid_t waitpid(pid_t pid, int *status, int options)
|
|||
return -1;
|
||||
}
|
||||
|
||||
int mingw_is_mount_point(struct strbuf *path)
|
||||
{
|
||||
WIN32_FIND_DATAW findbuf = { 0 };
|
||||
HANDLE handle;
|
||||
wchar_t wfilename[MAX_PATH];
|
||||
int wlen = xutftowcs_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;
|
||||
|
|
|
@ -449,6 +449,9 @@ static inline void convert_slashes(char *path)
|
|||
if (*path == '\\')
|
||||
*path = '/';
|
||||
}
|
||||
struct strbuf;
|
||||
int mingw_is_mount_point(struct strbuf *path);
|
||||
#define is_mount_point mingw_is_mount_point
|
||||
#define PATH_SEP ';'
|
||||
char *mingw_query_user_email(void);
|
||||
#define query_user_email mingw_query_user_email
|
||||
|
|
|
@ -540,6 +540,10 @@ static inline int git_has_dir_sep(const char *path)
|
|||
#define has_dir_sep(path) git_has_dir_sep(path)
|
||||
#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
39
path.c
|
@ -1300,6 +1300,45 @@ char *strip_path_suffix(const char *path, const char *suffix)
|
|||
return offset == -1 ? NULL : xstrndup(path, offset);
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -788,4 +788,13 @@ test_expect_success 'traverse into directories that may have ignored entries' '
|
|||
)
|
||||
'
|
||||
|
||||
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_file target/dont-clean-me
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
Загрузка…
Ссылка в новой задаче