Skip to content

Commit 32a748d

Browse files
authored
Merge pull request #2268 from drizzd/dont-clean-junctions
Avoid traversing NTFS junction points in `git clean -dfx`
2 parents 271c090 + b7ca314 commit 32a748d

File tree

9 files changed

+153
-2
lines changed

9 files changed

+153
-2
lines changed

builtin/clean.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ 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+
#ifndef CAN_UNLINK_MOUNT_POINTS
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");
39+
#endif
3640
static const char *msg_warn_remove_failed = N_("failed to remove %s");
3741

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

175+
if (is_mount_point(path)) {
176+
#ifndef CAN_UNLINK_MOUNT_POINTS
177+
if (!quiet) {
178+
quote_path_relative(path->buf, prefix, &quoted);
179+
printf(dry_run ?
180+
_(msg_would_skip_mount_point) :
181+
_(msg_skip_mount_point), quoted.buf);
182+
}
183+
*dir_gone = 0;
184+
#else
185+
if (!dry_run && unlink(path->buf)) {
186+
int saved_errno = errno;
187+
quote_path_relative(path->buf, prefix, &quoted);
188+
errno = saved_errno;
189+
warning_errno(_(msg_warn_remove_failed), quoted.buf);
190+
*dir_gone = 0;
191+
ret = -1;
192+
}
193+
#endif
194+
195+
goto out;
196+
}
197+
171198
dir = opendir(path->buf);
172199
if (!dir) {
173200
/* 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)
957984

958985
if (read_cache() < 0)
959986
die(_("index file corrupt"));
987+
enable_fscache(active_nr);
960988

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

1077+
disable_fscache();
10491078
strbuf_release(&abs_path);
10501079
strbuf_release(&buf);
10511080
string_list_clear(&del_list, 0);

cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
12671267
int normalize_path_copy(char *dst, const char *src);
12681268
int longest_ancestor_length(const char *path, struct string_list *prefixes);
12691269
char *strip_path_suffix(const char *path, const char *suffix);
1270+
int is_mount_point_via_stat(struct strbuf *path);
12701271
int daemon_avoid_alias(const char *path);
12711272

12721273
/*

compat/mingw.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2845,6 +2845,30 @@ pid_t waitpid(pid_t pid, int *status, int options)
28452845
return -1;
28462846
}
28472847

2848+
int (*win32_is_mount_point)(struct strbuf *path) = mingw_is_mount_point;
2849+
2850+
int mingw_is_mount_point(struct strbuf *path)
2851+
{
2852+
WIN32_FIND_DATAW findbuf = { 0 };
2853+
HANDLE handle;
2854+
wchar_t wfilename[MAX_LONG_PATH];
2855+
int wlen = xutftowcs_long_path(wfilename, path->buf);
2856+
if (wlen < 0)
2857+
die(_("could not get long path for '%s'"), path->buf);
2858+
2859+
/* remove trailing slash, if any */
2860+
if (wlen > 0 && wfilename[wlen - 1] == L'/')
2861+
wfilename[--wlen] = L'\0';
2862+
2863+
handle = FindFirstFileW(wfilename, &findbuf);
2864+
if (handle == INVALID_HANDLE_VALUE)
2865+
return 0;
2866+
FindClose(handle);
2867+
2868+
return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
2869+
(findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT);
2870+
}
2871+
28482872
int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
28492873
{
28502874
int upos = 0, wpos = 0;

compat/mingw.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,11 @@ static inline void convert_slashes(char *path)
463463
if (*path == '\\')
464464
*path = '/';
465465
}
466+
struct strbuf;
467+
int mingw_is_mount_point(struct strbuf *path);
468+
extern int (*win32_is_mount_point)(struct strbuf *path);
469+
#define is_mount_point win32_is_mount_point
470+
#define CAN_UNLINK_MOUNT_POINTS 1
466471
#define PATH_SEP ';'
467472
extern char *mingw_query_user_email(void);
468473
#define query_user_email mingw_query_user_email

compat/win32/fscache.c

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ static struct trace_key trace_fscache = TRACE_KEY_INIT(FSCACHE);
4141
struct fsentry {
4242
struct hashmap_entry ent;
4343
mode_t st_mode;
44+
ULONG reparse_tag;
4445
/* Length of name. */
4546
unsigned short len;
4647
/*
@@ -180,6 +181,10 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent
180181

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

184+
fse->reparse_tag =
185+
fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ?
186+
fdata->EaSize : 0;
187+
183188
/*
184189
* On certain Windows versions, host directories mapped into
185190
* 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
189194
* Let's work around this by detecting that situation and
190195
* telling Git that these are *not* symbolic links.
191196
*/
192-
if (fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
193-
fdata->EaSize == IO_REPARSE_TAG_SYMLINK &&
197+
if (fse->reparse_tag == IO_REPARSE_TAG_SYMLINK &&
194198
sizeof(buf) > (list ? list->len + 1 : 0) + fse->len + 1 &&
195199
is_inside_windows_container()) {
196200
size_t off = 0;
@@ -461,6 +465,7 @@ int fscache_enable(size_t initial_size)
461465
/* redirect opendir and lstat to the fscache implementations */
462466
opendir = fscache_opendir;
463467
lstat = fscache_lstat;
468+
win32_is_mount_point = fscache_is_mount_point;
464469
}
465470
initialized++;
466471
LeaveCriticalSection(&fscache_cs);
@@ -521,6 +526,7 @@ void fscache_disable(void)
521526
/* reset opendir and lstat to the original implementations */
522527
opendir = dirent_opendir;
523528
lstat = mingw_lstat;
529+
win32_is_mount_point = mingw_is_mount_point;
524530
}
525531
LeaveCriticalSection(&fscache_cs);
526532

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

597+
/*
598+
* is_mount_point() replacement, uses cache if enabled, otherwise falls
599+
* back to mingw_is_mount_point().
600+
*/
601+
int fscache_is_mount_point(struct strbuf *path)
602+
{
603+
int dirlen, base, len;
604+
struct fsentry key[2], *fse;
605+
struct fscache *cache = fscache_getcache();
606+
607+
if (!cache || !do_fscache_enabled(cache, path->buf))
608+
return mingw_is_mount_point(path);
609+
610+
cache->lstat_requests++;
611+
/* split path into path + name */
612+
len = path->len;
613+
if (len && is_dir_sep(path->buf[len - 1]))
614+
len--;
615+
base = len;
616+
while (base && !is_dir_sep(path->buf[base - 1]))
617+
base--;
618+
dirlen = base ? base - 1 : 0;
619+
620+
/* lookup entry for path + name in cache */
621+
fsentry_init(key, NULL, path->buf, dirlen);
622+
fsentry_init(key + 1, key, path->buf + base, len - base);
623+
fse = fscache_get(cache, key + 1);
624+
if (!fse)
625+
return mingw_is_mount_point(path);
626+
return fse->reparse_tag == IO_REPARSE_TAG_MOUNT_POINT;
627+
}
628+
591629
typedef struct fscache_DIR {
592630
struct DIR base_dir; /* extend base struct DIR */
593631
struct fsentry *pfsentry;

compat/win32/fscache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ void fscache_flush(void);
2222

2323
DIR *fscache_opendir(const char *dir);
2424
int fscache_lstat(const char *file_name, struct stat *buf);
25+
int fscache_is_mount_point(struct strbuf *path);
2526

2627
/* opaque fscache structure */
2728
struct fscache;

git-compat-util.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,10 @@ static inline int git_create_symlink(struct index_state *index, const char *targ
415415
#define create_symlink git_create_symlink
416416
#endif
417417

418+
#ifndef is_mount_point
419+
#define is_mount_point is_mount_point_via_stat
420+
#endif
421+
418422
#ifndef query_user_email
419423
#define query_user_email() NULL
420424
#endif

path.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,45 @@ char *strip_path_suffix(const char *path, const char *suffix)
12531253
return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
12541254
}
12551255

1256+
int is_mount_point_via_stat(struct strbuf *path)
1257+
{
1258+
size_t len = path->len;
1259+
unsigned int current_dev;
1260+
struct stat st;
1261+
1262+
if (!strcmp("/", path->buf))
1263+
return 1;
1264+
1265+
strbuf_addstr(path, "/.");
1266+
if (lstat(path->buf, &st)) {
1267+
/*
1268+
* If we cannot access the current directory, we cannot say
1269+
* that it is a bind mount.
1270+
*/
1271+
strbuf_setlen(path, len);
1272+
return 0;
1273+
}
1274+
current_dev = st.st_dev;
1275+
1276+
/* Now look at the parent directory */
1277+
strbuf_addch(path, '.');
1278+
if (lstat(path->buf, &st)) {
1279+
/*
1280+
* If we cannot access the parent directory, we cannot say
1281+
* that it is a bind mount.
1282+
*/
1283+
strbuf_setlen(path, len);
1284+
return 0;
1285+
}
1286+
strbuf_setlen(path, len);
1287+
1288+
/*
1289+
* If the device ID differs between current and parent directory,
1290+
* then it is a bind mount.
1291+
*/
1292+
return current_dev != st.st_dev;
1293+
}
1294+
12561295
int daemon_avoid_alias(const char *p)
12571296
{
12581297
int sl, ndot;

t/t7300-clean.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,4 +680,14 @@ test_expect_success MINGW 'handle clean & core.longpaths = false nicely' '
680680
grep "too long" .git/err
681681
'
682682

683+
test_expect_success MINGW 'clean does not traverse mount points' '
684+
mkdir target &&
685+
>target/dont-clean-me &&
686+
git init with-mountpoint &&
687+
cmd //c "mklink /j with-mountpoint\\mountpoint target" &&
688+
git -C with-mountpoint clean -dfx &&
689+
test_path_is_missing with-mountpoint/mountpoint &&
690+
test_path_is_file target/dont-clean-me
691+
'
692+
683693
test_done

0 commit comments

Comments
 (0)