Skip to content

Commit d2fd8bb

Browse files
committed
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 #607 Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 5501b4a commit d2fd8bb

File tree

7 files changed

+92
-0
lines changed

7 files changed

+92
-0
lines changed

builtin/clean.c

+14
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ static const char *msg_remove = N_("Removing %s\n");
3434
static const char *msg_would_remove = N_("Would remove %s\n");
3535
static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
3636
static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
37+
static const char *msg_skip_mount_point = N_("Skipping mount point %s\n");
38+
static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n");
3739
static const char *msg_warn_remove_failed = N_("failed to remove %s");
3840
static const char *msg_warn_lstat_failed = N_("could not lstat %s\n");
3941
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,
175177
goto out;
176178
}
177179

180+
if (is_mount_point(path)) {
181+
if (!quiet) {
182+
quote_path(path->buf, prefix, &quoted, 0);
183+
printf(dry_run ?
184+
_(msg_would_skip_mount_point) :
185+
_(msg_skip_mount_point), quoted.buf);
186+
}
187+
*dir_gone = 0;
188+
189+
goto out;
190+
}
191+
178192
dir = opendir(path->buf);
179193
if (!dir) {
180194
/* an empty dir could be removed even if it is unreadble */

cache.h

+1
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
12891289
int normalize_path_copy(char *dst, const char *src);
12901290
int longest_ancestor_length(const char *path, struct string_list *prefixes);
12911291
char *strip_path_suffix(const char *path, const char *suffix);
1292+
int is_mount_point_via_stat(struct strbuf *path);
12921293
int daemon_avoid_alias(const char *path);
12931294

12941295
/*

compat/mingw.c

+22
Original file line numberDiff line numberDiff line change
@@ -2509,6 +2509,28 @@ pid_t waitpid(pid_t pid, int *status, int options)
25092509
return -1;
25102510
}
25112511

2512+
int mingw_is_mount_point(struct strbuf *path)
2513+
{
2514+
WIN32_FIND_DATAW findbuf = { 0 };
2515+
HANDLE handle;
2516+
wchar_t wfilename[MAX_PATH];
2517+
int wlen = xutftowcs_path(wfilename, path->buf);
2518+
if (wlen < 0)
2519+
die(_("could not get long path for '%s'"), path->buf);
2520+
2521+
/* remove trailing slash, if any */
2522+
if (wlen > 0 && wfilename[wlen - 1] == L'/')
2523+
wfilename[--wlen] = L'\0';
2524+
2525+
handle = FindFirstFileW(wfilename, &findbuf);
2526+
if (handle == INVALID_HANDLE_VALUE)
2527+
return 0;
2528+
FindClose(handle);
2529+
2530+
return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
2531+
(findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT);
2532+
}
2533+
25122534
int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
25132535
{
25142536
int upos = 0, wpos = 0;

compat/mingw.h

+3
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,9 @@ static inline void convert_slashes(char *path)
449449
if (*path == '\\')
450450
*path = '/';
451451
}
452+
struct strbuf;
453+
int mingw_is_mount_point(struct strbuf *path);
454+
#define is_mount_point mingw_is_mount_point
452455
#define PATH_SEP ';'
453456
char *mingw_query_user_email(void);
454457
#define query_user_email mingw_query_user_email

git-compat-util.h

+4
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,10 @@ static inline int git_has_dir_sep(const char *path)
548548
#define has_dir_sep(path) git_has_dir_sep(path)
549549
#endif
550550

551+
#ifndef is_mount_point
552+
#define is_mount_point is_mount_point_via_stat
553+
#endif
554+
551555
#ifndef query_user_email
552556
#define query_user_email() NULL
553557
#endif

path.c

+39
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,45 @@ char *strip_path_suffix(const char *path, const char *suffix)
13061306
return offset == -1 ? NULL : xstrndup(path, offset);
13071307
}
13081308

1309+
int is_mount_point_via_stat(struct strbuf *path)
1310+
{
1311+
size_t len = path->len;
1312+
unsigned int current_dev;
1313+
struct stat st;
1314+
1315+
if (!strcmp("/", path->buf))
1316+
return 1;
1317+
1318+
strbuf_addstr(path, "/.");
1319+
if (lstat(path->buf, &st)) {
1320+
/*
1321+
* If we cannot access the current directory, we cannot say
1322+
* that it is a bind mount.
1323+
*/
1324+
strbuf_setlen(path, len);
1325+
return 0;
1326+
}
1327+
current_dev = st.st_dev;
1328+
1329+
/* Now look at the parent directory */
1330+
strbuf_addch(path, '.');
1331+
if (lstat(path->buf, &st)) {
1332+
/*
1333+
* If we cannot access the parent directory, we cannot say
1334+
* that it is a bind mount.
1335+
*/
1336+
strbuf_setlen(path, len);
1337+
return 0;
1338+
}
1339+
strbuf_setlen(path, len);
1340+
1341+
/*
1342+
* If the device ID differs between current and parent directory,
1343+
* then it is a bind mount.
1344+
*/
1345+
return current_dev != st.st_dev;
1346+
}
1347+
13091348
int daemon_avoid_alias(const char *p)
13101349
{
13111350
int sl, ndot;

t/t7300-clean.sh

+9
Original file line numberDiff line numberDiff line change
@@ -789,4 +789,13 @@ test_expect_success 'traverse into directories that may have ignored entries' '
789789
)
790790
'
791791

792+
test_expect_success MINGW 'clean does not traverse mount points' '
793+
mkdir target &&
794+
>target/dont-clean-me &&
795+
git init with-mountpoint &&
796+
cmd //c "mklink /j with-mountpoint\\mountpoint target" &&
797+
git -C with-mountpoint clean -dfx &&
798+
test_path_is_file target/dont-clean-me
799+
'
800+
792801
test_done

0 commit comments

Comments
 (0)