Skip to content

Commit bdb033a

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 f9af099 + d0dd3fa commit bdb033a

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
@@ -34,6 +34,10 @@ 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+
#ifndef CAN_UNLINK_MOUNT_POINTS
38+
static const char *msg_skip_mount_point = N_("Skipping mount point %s\n");
39+
static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n");
40+
#endif
3741
static const char *msg_warn_remove_failed = N_("failed to remove %s");
3842
static const char *msg_warn_lstat_failed = N_("could not lstat %s\n");
3943

@@ -171,6 +175,29 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
171175
goto out;
172176
}
173177

178+
if (is_mount_point(path)) {
179+
#ifndef CAN_UNLINK_MOUNT_POINTS
180+
if (!quiet) {
181+
quote_path(path->buf, prefix, &quoted, 0);
182+
printf(dry_run ?
183+
_(msg_would_skip_mount_point) :
184+
_(msg_skip_mount_point), quoted.buf);
185+
}
186+
*dir_gone = 0;
187+
#else
188+
if (!dry_run && unlink(path->buf)) {
189+
int saved_errno = errno;
190+
quote_path(path->buf, prefix, &quoted, 0);
191+
errno = saved_errno;
192+
warning_errno(_(msg_warn_remove_failed), quoted.buf);
193+
*dir_gone = 0;
194+
ret = -1;
195+
}
196+
#endif
197+
198+
goto out;
199+
}
200+
174201
dir = opendir(path->buf);
175202
if (!dir) {
176203
/* 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
@@ -1266,6 +1266,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
12661266
int normalize_path_copy(char *dst, const char *src);
12671267
int longest_ancestor_length(const char *path, struct string_list *prefixes);
12681268
char *strip_path_suffix(const char *path, const char *suffix);
1269+
int is_mount_point_via_stat(struct strbuf *path);
12691270
int daemon_avoid_alias(const char *path);
12701271

12711272
/*

compat/mingw.c

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

2474+
int mingw_is_mount_point(struct strbuf *path)
2475+
{
2476+
WIN32_FIND_DATAW findbuf = { 0 };
2477+
HANDLE handle;
2478+
wchar_t wfilename[MAX_PATH];
2479+
int wlen = xutftowcs_path(wfilename, path->buf);
2480+
if (wlen < 0)
2481+
die(_("could not get long path for '%s'"), path->buf);
2482+
2483+
/* remove trailing slash, if any */
2484+
if (wlen > 0 && wfilename[wlen - 1] == L'/')
2485+
wfilename[--wlen] = L'\0';
2486+
2487+
handle = FindFirstFileW(wfilename, &findbuf);
2488+
if (handle == INVALID_HANDLE_VALUE)
2489+
return 0;
2490+
FindClose(handle);
2491+
2492+
return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
2493+
(findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT);
2494+
}
2495+
24742496
int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
24752497
{
24762498
int upos = 0, wpos = 0;

compat/mingw.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,10 @@ static inline void convert_slashes(char *path)
443443
if (*path == '\\')
444444
*path = '/';
445445
}
446+
struct strbuf;
447+
int mingw_is_mount_point(struct strbuf *path);
448+
#define is_mount_point mingw_is_mount_point
449+
#define CAN_UNLINK_MOUNT_POINTS 1
446450
#define PATH_SEP ';'
447451
char *mingw_query_user_email(void);
448452
#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
@@ -411,6 +411,10 @@ static inline int git_has_dir_sep(const char *path)
411411
#define has_dir_sep(path) git_has_dir_sep(path)
412412
#endif
413413

414+
#ifndef is_mount_point
415+
#define is_mount_point is_mount_point_via_stat
416+
#endif
417+
414418
#ifndef query_user_email
415419
#define query_user_email() NULL
416420
#endif

path.c

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

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

t/t7300-clean.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,4 +788,14 @@ test_expect_success 'traverse into directories that may have ignored entries' '
788788
)
789789
'
790790

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

0 commit comments

Comments
 (0)