Skip to content

Commit 99ffa42

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 230e4ca commit 99ffa42

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
@@ -40,6 +40,8 @@ static const char *msg_remove = N_("Removing %s\n");
4040
static const char *msg_would_remove = N_("Would remove %s\n");
4141
static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
4242
static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
43+
static const char *msg_skip_mount_point = N_("Skipping mount point %s\n");
44+
static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n");
4345
static const char *msg_warn_remove_failed = N_("failed to remove %s");
4446
static const char *msg_warn_lstat_failed = N_("could not lstat %s\n");
4547
static const char *msg_skip_cwd = N_("Refusing to remove current working directory\n");
@@ -184,6 +186,18 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
184186
goto out;
185187
}
186188

189+
if (is_mount_point(path)) {
190+
if (!quiet) {
191+
quote_path(path->buf, prefix, &quoted, 0);
192+
printf(dry_run ?
193+
_(msg_would_skip_mount_point) :
194+
_(msg_skip_mount_point), quoted.buf);
195+
}
196+
*dir_gone = 0;
197+
198+
goto out;
199+
}
200+
187201
dir = opendir(path->buf);
188202
if (!dir) {
189203
/* an empty dir could be removed even if it is unreadble */

compat/mingw.c

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

2689+
int mingw_is_mount_point(struct strbuf *path)
2690+
{
2691+
WIN32_FIND_DATAW findbuf = { 0 };
2692+
HANDLE handle;
2693+
wchar_t wfilename[MAX_PATH];
2694+
int wlen = xutftowcs_path(wfilename, path->buf);
2695+
if (wlen < 0)
2696+
die(_("could not get long path for '%s'"), path->buf);
2697+
2698+
/* remove trailing slash, if any */
2699+
if (wlen > 0 && wfilename[wlen - 1] == L'/')
2700+
wfilename[--wlen] = L'\0';
2701+
2702+
handle = FindFirstFileW(wfilename, &findbuf);
2703+
if (handle == INVALID_HANDLE_VALUE)
2704+
return 0;
2705+
FindClose(handle);
2706+
2707+
return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
2708+
(findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT);
2709+
}
2710+
26892711
int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
26902712
{
26912713
int upos = 0, wpos = 0;

compat/mingw.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,9 @@ static inline void convert_slashes(char *path)
454454
if (*path == '\\')
455455
*path = '/';
456456
}
457+
struct strbuf;
458+
int mingw_is_mount_point(struct strbuf *path);
459+
#define is_mount_point mingw_is_mount_point
457460
#define PATH_SEP ';'
458461
char *mingw_query_user_email(void);
459462
#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
@@ -626,6 +626,10 @@ static inline int git_has_dir_sep(const char *path)
626626
#define has_dir_sep(path) git_has_dir_sep(path)
627627
#endif
628628

629+
#ifndef is_mount_point
630+
#define is_mount_point is_mount_point_via_stat
631+
#endif
632+
629633
#ifndef query_user_email
630634
#define query_user_email() NULL
631635
#endif

path.c

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

1229+
int is_mount_point_via_stat(struct strbuf *path)
1230+
{
1231+
size_t len = path->len;
1232+
dev_t current_dev;
1233+
struct stat st;
1234+
1235+
if (!strcmp("/", path->buf))
1236+
return 1;
1237+
1238+
strbuf_addstr(path, "/.");
1239+
if (lstat(path->buf, &st)) {
1240+
/*
1241+
* If we cannot access the current directory, we cannot say
1242+
* that it is a bind mount.
1243+
*/
1244+
strbuf_setlen(path, len);
1245+
return 0;
1246+
}
1247+
current_dev = st.st_dev;
1248+
1249+
/* Now look at the parent directory */
1250+
strbuf_addch(path, '.');
1251+
if (lstat(path->buf, &st)) {
1252+
/*
1253+
* If we cannot access the parent directory, we cannot say
1254+
* that it is a bind mount.
1255+
*/
1256+
strbuf_setlen(path, len);
1257+
return 0;
1258+
}
1259+
strbuf_setlen(path, len);
1260+
1261+
/*
1262+
* If the device ID differs between current and parent directory,
1263+
* then it is a bind mount.
1264+
*/
1265+
return current_dev != st.st_dev;
1266+
}
1267+
12291268
int daemon_avoid_alias(const char *p)
12301269
{
12311270
int sl, ndot;

path.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ int normalize_path_copy(char *dst, const char *src);
183183
int strbuf_normalize_path(struct strbuf *src);
184184
int longest_ancestor_length(const char *path, struct string_list *prefixes);
185185
char *strip_path_suffix(const char *path, const char *suffix);
186+
int is_mount_point_via_stat(struct strbuf *path);
186187
int daemon_avoid_alias(const char *path);
187188

188189
/*

t/t7300-clean.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,4 +800,13 @@ test_expect_success 'traverse into directories that may have ignored entries' '
800800
)
801801
'
802802

803+
test_expect_success MINGW 'clean does not traverse mount points' '
804+
mkdir target &&
805+
>target/dont-clean-me &&
806+
git init with-mountpoint &&
807+
cmd //c "mklink /j with-mountpoint\\mountpoint target" &&
808+
git -C with-mountpoint clean -dfx &&
809+
test_path_is_file target/dont-clean-me
810+
'
811+
803812
test_done

0 commit comments

Comments
 (0)