Skip to content

Commit 566105d

Browse files
dschoGit for Windows Build Agent
authored andcommitted
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 6e2bcc8 commit 566105d

File tree

7 files changed

+92
-0
lines changed

7 files changed

+92
-0
lines changed

builtin/clean.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ static const char *msg_remove = N_("Removing %s\n");
3333
static const char *msg_would_remove = N_("Would remove %s\n");
3434
static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
3535
static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
36+
static const char *msg_skip_mount_point = N_("Skipping mount point %s\n");
37+
static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n");
3638
static const char *msg_warn_remove_failed = N_("failed to remove %s");
3739
static const char *msg_warn_lstat_failed = N_("could not lstat %s\n");
3840

@@ -170,6 +172,18 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
170172
goto out;
171173
}
172174

175+
if (is_mount_point(path)) {
176+
if (!quiet) {
177+
quote_path_relative(path->buf, prefix, &quoted);
178+
printf(dry_run ?
179+
_(msg_would_skip_mount_point) :
180+
_(msg_skip_mount_point), quoted.buf);
181+
}
182+
*dir_gone = 0;
183+
184+
goto out;
185+
}
186+
173187
dir = opendir(path->buf);
174188
if (!dir) {
175189
/* an empty dir could be removed even if it is unreadble */

cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
13251325
int normalize_path_copy(char *dst, const char *src);
13261326
int longest_ancestor_length(const char *path, struct string_list *prefixes);
13271327
char *strip_path_suffix(const char *path, const char *suffix);
1328+
int is_mount_point_via_stat(struct strbuf *path);
13281329
int daemon_avoid_alias(const char *path);
13291330

13301331
/*

compat/mingw.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2408,6 +2408,28 @@ pid_t waitpid(pid_t pid, int *status, int options)
24082408
return -1;
24092409
}
24102410

2411+
int mingw_is_mount_point(struct strbuf *path)
2412+
{
2413+
WIN32_FIND_DATAW findbuf = { 0 };
2414+
HANDLE handle;
2415+
wchar_t wfilename[MAX_PATH];
2416+
int wlen = xutftowcs_path(wfilename, path->buf);
2417+
if (wlen < 0)
2418+
die(_("could not get long path for '%s'"), path->buf);
2419+
2420+
/* remove trailing slash, if any */
2421+
if (wlen > 0 && wfilename[wlen - 1] == L'/')
2422+
wfilename[--wlen] = L'\0';
2423+
2424+
handle = FindFirstFileW(wfilename, &findbuf);
2425+
if (handle == INVALID_HANDLE_VALUE)
2426+
return 0;
2427+
FindClose(handle);
2428+
2429+
return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
2430+
(findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT);
2431+
}
2432+
24112433
int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
24122434
{
24132435
int upos = 0, wpos = 0;

compat/mingw.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,9 @@ static inline void convert_slashes(char *path)
442442
if (*path == '\\')
443443
*path = '/';
444444
}
445+
struct strbuf;
446+
int mingw_is_mount_point(struct strbuf *path);
447+
#define is_mount_point mingw_is_mount_point
445448
#define PATH_SEP ';'
446449
char *mingw_query_user_email(void);
447450
#define query_user_email mingw_query_user_email

git-compat-util.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,10 @@ static inline char *git_find_last_dir_sep(const char *path)
389389
#define find_last_dir_sep git_find_last_dir_sep
390390
#endif
391391

392+
#ifndef is_mount_point
393+
#define is_mount_point is_mount_point_via_stat
394+
#endif
395+
392396
#ifndef query_user_email
393397
#define query_user_email() NULL
394398
#endif

path.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,45 @@ char *strip_path_suffix(const char *path, const char *suffix)
12891289
return offset == -1 ? NULL : xstrndup(path, offset);
12901290
}
12911291

1292+
int is_mount_point_via_stat(struct strbuf *path)
1293+
{
1294+
size_t len = path->len;
1295+
unsigned int current_dev;
1296+
struct stat st;
1297+
1298+
if (!strcmp("/", path->buf))
1299+
return 1;
1300+
1301+
strbuf_addstr(path, "/.");
1302+
if (lstat(path->buf, &st)) {
1303+
/*
1304+
* If we cannot access the current directory, we cannot say
1305+
* that it is a bind mount.
1306+
*/
1307+
strbuf_setlen(path, len);
1308+
return 0;
1309+
}
1310+
current_dev = st.st_dev;
1311+
1312+
/* Now look at the parent directory */
1313+
strbuf_addch(path, '.');
1314+
if (lstat(path->buf, &st)) {
1315+
/*
1316+
* If we cannot access the parent directory, we cannot say
1317+
* that it is a bind mount.
1318+
*/
1319+
strbuf_setlen(path, len);
1320+
return 0;
1321+
}
1322+
strbuf_setlen(path, len);
1323+
1324+
/*
1325+
* If the device ID differs between current and parent directory,
1326+
* then it is a bind mount.
1327+
*/
1328+
return current_dev != st.st_dev;
1329+
}
1330+
12921331
int daemon_avoid_alias(const char *p)
12931332
{
12941333
int sl, ndot;

t/t7300-clean.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,4 +746,13 @@ test_expect_success 'clean untracked paths by pathspec' '
746746
test_must_be_empty actual
747747
'
748748

749+
test_expect_success MINGW 'clean does not traverse mount points' '
750+
mkdir target &&
751+
>target/dont-clean-me &&
752+
git init with-mountpoint &&
753+
cmd //c "mklink /j with-mountpoint\\mountpoint target" &&
754+
git -C with-mountpoint clean -dfx &&
755+
test_path_is_file target/dont-clean-me
756+
'
757+
749758
test_done

0 commit comments

Comments
 (0)