Skip to content

Commit 0e7bc9c

Browse files
committed
Merge branch 'dont-clean-junctions'
This topic branch teaches `git clean` to respect NTFS junctions and Unix bind mounts: it will now stop at those boundaries. Signed-off-by: Johannes Schindelin <[email protected]>
2 parents 2ec0511 + d72fc0e commit 0e7bc9c

File tree

7 files changed

+107
-0
lines changed

7 files changed

+107
-0
lines changed

builtin/clean.c

Lines changed: 27 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
static const char *msg_warn_lstat_failed = N_("could not lstat %s\n");
3842

@@ -169,6 +173,29 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
169173
goto out;
170174
}
171175

176+
if (is_mount_point(path)) {
177+
#ifndef CAN_UNLINK_MOUNT_POINTS
178+
if (!quiet) {
179+
quote_path_relative(path->buf, prefix, &quoted);
180+
printf(dry_run ?
181+
_(msg_would_skip_mount_point) :
182+
_(msg_skip_mount_point), quoted.buf);
183+
}
184+
*dir_gone = 0;
185+
#else
186+
if (!dry_run && unlink(path->buf)) {
187+
int saved_errno = errno;
188+
quote_path_relative(path->buf, prefix, &quoted);
189+
errno = saved_errno;
190+
warning_errno(_(msg_warn_remove_failed), quoted.buf);
191+
*dir_gone = 0;
192+
ret = -1;
193+
}
194+
#endif
195+
196+
goto out;
197+
}
198+
172199
dir = opendir(path->buf);
173200
if (!dir) {
174201
/* 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
@@ -1261,6 +1261,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
12611261
int normalize_path_copy(char *dst, const char *src);
12621262
int longest_ancestor_length(const char *path, struct string_list *prefixes);
12631263
char *strip_path_suffix(const char *path, const char *suffix);
1264+
int is_mount_point_via_stat(struct strbuf *path);
12641265
int daemon_avoid_alias(const char *path);
12651266

12661267
/*

compat/mingw.c

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

2282+
int mingw_is_mount_point(struct strbuf *path)
2283+
{
2284+
WIN32_FIND_DATAW findbuf = { 0 };
2285+
HANDLE handle;
2286+
wchar_t wfilename[MAX_LONG_PATH];
2287+
int wlen = xutftowcs_long_path(wfilename, path->buf);
2288+
if (wlen < 0)
2289+
die(_("could not get long path for '%s'"), path->buf);
2290+
2291+
/* remove trailing slash, if any */
2292+
if (wlen > 0 && wfilename[wlen - 1] == L'/')
2293+
wfilename[--wlen] = L'\0';
2294+
2295+
handle = FindFirstFileW(wfilename, &findbuf);
2296+
if (handle == INVALID_HANDLE_VALUE)
2297+
return 0;
2298+
FindClose(handle);
2299+
2300+
return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
2301+
(findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT);
2302+
}
2303+
22822304
int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
22832305
{
22842306
int upos = 0, wpos = 0;

compat/mingw.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,10 @@ 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
448+
#define CAN_UNLINK_MOUNT_POINTS 1
445449
#define PATH_SEP ';'
446450
extern char *mingw_query_user_email(void);
447451
#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
@@ -404,6 +404,10 @@ static inline char *git_find_last_dir_sep(const char *path)
404404
#define find_last_dir_sep git_find_last_dir_sep
405405
#endif
406406

407+
#ifndef is_mount_point
408+
#define is_mount_point is_mount_point_via_stat
409+
#endif
410+
407411
#ifndef query_user_email
408412
#define query_user_email() NULL
409413
#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
@@ -681,4 +681,14 @@ test_expect_success MINGW 'handle clean & core.longpaths = false nicely' '
681681
test_i18ngrep "too long" .git/err
682682
'
683683

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

0 commit comments

Comments
 (0)