Skip to content

Commit

Permalink
Merge branch 'dont-clean-junctions'
Browse files Browse the repository at this point in the history
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 <johannes.schindelin@gmx.de>
  • Loading branch information
dscho authored and Git for Windows Build Agent committed Jul 2, 2024
2 parents e2f19a1 + 85f1f4c commit 901b4b4
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 0 deletions.
27 changes: 27 additions & 0 deletions builtin/clean.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ static const char *msg_remove = N_("Removing %s\n");
static const char *msg_would_remove = N_("Would remove %s\n");
static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
#ifndef CAN_UNLINK_MOUNT_POINTS
static const char *msg_skip_mount_point = N_("Skipping mount point %s\n");
static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n");
#endif
static const char *msg_warn_remove_failed = N_("failed to remove %s");
static const char *msg_warn_lstat_failed = N_("could not lstat %s\n");
static const char *msg_skip_cwd = N_("Refusing to remove current working directory\n");
Expand Down Expand Up @@ -182,6 +186,29 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
goto out;
}

if (is_mount_point(path)) {
#ifndef CAN_UNLINK_MOUNT_POINTS
if (!quiet) {
quote_path(path->buf, prefix, &quoted, 0);
printf(dry_run ?
_(msg_would_skip_mount_point) :
_(msg_skip_mount_point), quoted.buf);
}
*dir_gone = 0;
#else
if (!dry_run && unlink(path->buf)) {
int saved_errno = errno;
quote_path(path->buf, prefix, &quoted, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
ret = -1;
}
#endif

goto out;
}

dir = opendir(path->buf);
if (!dir) {
/* an empty dir could be removed even if it is unreadble */
Expand Down
22 changes: 22 additions & 0 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -2541,6 +2541,28 @@ pid_t waitpid(pid_t pid, int *status, int options)
return -1;
}

int mingw_is_mount_point(struct strbuf *path)
{
WIN32_FIND_DATAW findbuf = { 0 };
HANDLE handle;
wchar_t wfilename[MAX_PATH];
int wlen = xutftowcs_path(wfilename, path->buf);
if (wlen < 0)
die(_("could not get long path for '%s'"), path->buf);

/* remove trailing slash, if any */
if (wlen > 0 && wfilename[wlen - 1] == L'/')
wfilename[--wlen] = L'\0';

handle = FindFirstFileW(wfilename, &findbuf);
if (handle == INVALID_HANDLE_VALUE)
return 0;
FindClose(handle);

return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
(findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT);
}

int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
{
int upos = 0, wpos = 0;
Expand Down
4 changes: 4 additions & 0 deletions compat/mingw.h
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,10 @@ static inline void convert_slashes(char *path)
if (*path == '\\')
*path = '/';
}
struct strbuf;
int mingw_is_mount_point(struct strbuf *path);
#define is_mount_point mingw_is_mount_point
#define CAN_UNLINK_MOUNT_POINTS 1
#define PATH_SEP ';'
char *mingw_query_user_email(void);
#define query_user_email mingw_query_user_email
Expand Down
4 changes: 4 additions & 0 deletions git-compat-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,10 @@ static inline int git_has_dir_sep(const char *path)
#define has_dir_sep(path) git_has_dir_sep(path)
#endif

#ifndef is_mount_point
#define is_mount_point is_mount_point_via_stat
#endif

#ifndef query_user_email
#define query_user_email() NULL
#endif
Expand Down
39 changes: 39 additions & 0 deletions path.c
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,45 @@ char *strip_path_suffix(const char *path, const char *suffix)
return offset == -1 ? NULL : xstrndup(path, offset);
}

int is_mount_point_via_stat(struct strbuf *path)
{
size_t len = path->len;
unsigned int current_dev;
struct stat st;

if (!strcmp("/", path->buf))
return 1;

strbuf_addstr(path, "/.");
if (lstat(path->buf, &st)) {
/*
* If we cannot access the current directory, we cannot say
* that it is a bind mount.
*/
strbuf_setlen(path, len);
return 0;
}
current_dev = st.st_dev;

/* Now look at the parent directory */
strbuf_addch(path, '.');
if (lstat(path->buf, &st)) {
/*
* If we cannot access the parent directory, we cannot say
* that it is a bind mount.
*/
strbuf_setlen(path, len);
return 0;
}
strbuf_setlen(path, len);

/*
* If the device ID differs between current and parent directory,
* then it is a bind mount.
*/
return current_dev != st.st_dev;
}

int daemon_avoid_alias(const char *p)
{
int sl, ndot;
Expand Down
1 change: 1 addition & 0 deletions path.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ int normalize_path_copy(char *dst, const char *src);
int strbuf_normalize_path(struct strbuf *src);
int longest_ancestor_length(const char *path, struct string_list *prefixes);
char *strip_path_suffix(const char *path, const char *suffix);
int is_mount_point_via_stat(struct strbuf *path);
int daemon_avoid_alias(const char *path);

/*
Expand Down
10 changes: 10 additions & 0 deletions t/t7300-clean.sh
Original file line number Diff line number Diff line change
Expand Up @@ -801,4 +801,14 @@ test_expect_success 'traverse into directories that may have ignored entries' '
)
'

test_expect_success MINGW 'clean does not traverse mount points' '
mkdir target &&
>target/dont-clean-me &&
git init with-mountpoint &&
cmd //c "mklink /j with-mountpoint\\mountpoint target" &&
git -C with-mountpoint clean -dfx &&
test_path_is_missing with-mountpoint/mountpoint &&
test_path_is_file target/dont-clean-me
'

test_done

0 comments on commit 901b4b4

Please sign in to comment.