Skip to content

Commit 357cc65

Browse files
dschoGit for Windows Build Agent
authored andcommitted
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 5bf18f6 + 052595b commit 357cc65

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
@@ -40,6 +40,10 @@ 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+
#ifndef CAN_UNLINK_MOUNT_POINTS
44+
static const char *msg_skip_mount_point = N_("Skipping mount point %s\n");
45+
static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n");
46+
#endif
4347
static const char *msg_warn_remove_failed = N_("failed to remove %s");
4448
static const char *msg_warn_lstat_failed = N_("could not lstat %s\n");
4549
static const char *msg_skip_cwd = N_("Refusing to remove current working directory\n");
@@ -184,6 +188,29 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
184188
goto out;
185189
}
186190

191+
if (is_mount_point(path)) {
192+
#ifndef CAN_UNLINK_MOUNT_POINTS
193+
if (!quiet) {
194+
quote_path(path->buf, prefix, &quoted, 0);
195+
printf(dry_run ?
196+
_(msg_would_skip_mount_point) :
197+
_(msg_skip_mount_point), quoted.buf);
198+
}
199+
*dir_gone = 0;
200+
#else
201+
if (!dry_run && unlink(path->buf)) {
202+
int saved_errno = errno;
203+
quote_path(path->buf, prefix, &quoted, 0);
204+
errno = saved_errno;
205+
warning_errno(_(msg_warn_remove_failed), quoted.buf);
206+
*dir_gone = 0;
207+
ret = -1;
208+
}
209+
#endif
210+
211+
goto out;
212+
}
213+
187214
dir = opendir(path->buf);
188215
if (!dir) {
189216
/* 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
@@ -2719,6 +2719,28 @@ pid_t waitpid(pid_t pid, int *status, int options)
27192719
return -1;
27202720
}
27212721

2722+
int mingw_is_mount_point(struct strbuf *path)
2723+
{
2724+
WIN32_FIND_DATAW findbuf = { 0 };
2725+
HANDLE handle;
2726+
wchar_t wfilename[MAX_PATH];
2727+
int wlen = xutftowcs_path(wfilename, path->buf);
2728+
if (wlen < 0)
2729+
die(_("could not get long path for '%s'"), path->buf);
2730+
2731+
/* remove trailing slash, if any */
2732+
if (wlen > 0 && wfilename[wlen - 1] == L'/')
2733+
wfilename[--wlen] = L'\0';
2734+
2735+
handle = FindFirstFileW(wfilename, &findbuf);
2736+
if (handle == INVALID_HANDLE_VALUE)
2737+
return 0;
2738+
FindClose(handle);
2739+
2740+
return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
2741+
(findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT);
2742+
}
2743+
27222744
int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
27232745
{
27242746
int upos = 0, wpos = 0;

compat/mingw.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ static inline void convert_slashes(char *path)
3636
if (*path == '\\')
3737
*path = '/';
3838
}
39+
struct strbuf;
40+
int mingw_is_mount_point(struct strbuf *path);
41+
#define is_mount_point mingw_is_mount_point
42+
#define CAN_UNLINK_MOUNT_POINTS 1
3943
#define PATH_SEP ';'
4044
char *mingw_query_user_email(void);
4145
#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
@@ -383,6 +383,10 @@ static inline int git_has_dir_sep(const char *path)
383383
#define has_dir_sep(path) git_has_dir_sep(path)
384384
#endif
385385

386+
#ifndef is_mount_point
387+
#define is_mount_point is_mount_point_via_stat
388+
#endif
389+
386390
#ifndef query_user_email
387391
#define query_user_email() NULL
388392
#endif

path.c

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

1426+
int is_mount_point_via_stat(struct strbuf *path)
1427+
{
1428+
size_t len = path->len;
1429+
dev_t current_dev;
1430+
struct stat st;
1431+
1432+
if (!strcmp("/", path->buf))
1433+
return 1;
1434+
1435+
strbuf_addstr(path, "/.");
1436+
if (lstat(path->buf, &st)) {
1437+
/*
1438+
* If we cannot access the current directory, we cannot say
1439+
* that it is a bind mount.
1440+
*/
1441+
strbuf_setlen(path, len);
1442+
return 0;
1443+
}
1444+
current_dev = st.st_dev;
1445+
1446+
/* Now look at the parent directory */
1447+
strbuf_addch(path, '.');
1448+
if (lstat(path->buf, &st)) {
1449+
/*
1450+
* If we cannot access the parent directory, we cannot say
1451+
* that it is a bind mount.
1452+
*/
1453+
strbuf_setlen(path, len);
1454+
return 0;
1455+
}
1456+
strbuf_setlen(path, len);
1457+
1458+
/*
1459+
* If the device ID differs between current and parent directory,
1460+
* then it is a bind mount.
1461+
*/
1462+
return current_dev != st.st_dev;
1463+
}
1464+
14261465
int daemon_avoid_alias(const char *p)
14271466
{
14281467
int sl, ndot;

path.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ int normalize_path_copy(char *dst, const char *src);
172172
int strbuf_normalize_path(struct strbuf *src);
173173
int longest_ancestor_length(const char *path, struct string_list *prefixes);
174174
char *strip_path_suffix(const char *path, const char *suffix);
175+
int is_mount_point_via_stat(struct strbuf *path);
175176
int daemon_avoid_alias(const char *path);
176177

177178
/*

t/t7300-clean.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,4 +800,14 @@ 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_missing with-mountpoint/mountpoint &&
810+
test_path_is_file target/dont-clean-me
811+
'
812+
803813
test_done

0 commit comments

Comments
 (0)