forked from git/git
-
Notifications
You must be signed in to change notification settings - Fork 162
Support symbolic links on Windows #2018
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dscho
wants to merge
34
commits into
gitgitgadget:js/prep-symlink-windows
Choose a base branch
from
dscho:symlinks-next
base: js/prep-symlink-windows
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 19 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
9693157
t9700: accommodate for Windows paths
dscho 33ccd0f
apply: symbolic links lack a "trustable executable bit"
dscho 78d52c8
mingw: special-case `open(symlink, O_CREAT | O_EXCL)`
dscho c8fb79c
t0001: handle `diff --no-index` gracefully
dscho d2de5d9
t0301: another fix for Windows compatibility
dscho 14b033f
t0600: fix incomplete prerequisite for a test case
dscho c670ae3
t1006: accommodate for symlink support in MSYS2
dscho d265cde
t1305: skip symlink tests that do not apply to Windows
dscho 02ea262
t6423: introduce Windows-specific handling for symlinking to /dev/null
dscho a4c7170
t7800: work around the MSYS path conversion on Windows
dscho 359de97
Merge branch 'js/test-symlink-windows' into js/prep-symlink-windows
gitster df27101
mingw: do resolve symlinks in `getcwd()`
dscho 722e65d
init: do parse _all_ core.* settings early
dscho f6b8e4a
strbuf_readlink(): avoid calling `readlink()` twice in corner-cases
kblees 9ce11d9
strbuf_readlink(): support link targets that exceed PATH_MAX
kblees 6f6fe02
trim_last_path_component(): avoid hard-coding the directory separator
kblees dae450d
mingw: don't call `GetFileAttributes()` twice in `mingw_lstat()`
kblees c36848e
mingw: implement `stat()` with symlink support
kblees aa0ca80
mingw: drop the separate `do_lstat()` function
kblees 8860443
mingw: let `mingw_lstat()` error early upon problems with reparse points
kblees db1d156
mingw: teach dirent about symlinks
kblees 4c49a3d
mingw: compute the correct size for symlinks in `mingw_lstat()`
billziss-gh ad74d54
mingw: factor out the retry logic
kblees 25313ce
mingw: change default of `core.symlinks` to false
kblees b698f4a
mingw: add symlink-specific error codes
kblees 282aba4
mingw: handle symlinks to directories in `mingw_unlink()`
kblees 5cb3b10
mingw: support renaming symlinks
kblees 4992083
mingw: allow `mingw_chdir()` to change to symlink-resolved directories
kblees 8fef822
mingw: implement `readlink()`
kblees 1dd5f9d
mingw: implement basic `symlink()` functionality (file symlinks only)
kblees 7b6dbc7
mingw: add support for symlinks to directories
kblees d3b89c2
mingw: try to create symlinks without elevated permissions
dscho 2e73ab4
mingw: emulate `stat()` a little more faithfully
dscho 817f488
mingw: special-case index entries for symlinks with buggy size
dscho File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -629,6 +629,7 @@ int mingw_open (const char *filename, int oflags, ...) | |
| int fd, create = (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL); | ||
| wchar_t wfilename[MAX_PATH]; | ||
| open_fn_t open_fn; | ||
| WIN32_FILE_ATTRIBUTE_DATA fdata; | ||
|
|
||
| DECLARE_PROC_ADDR(ntdll.dll, NTSTATUS, NTAPI, RtlGetLastNtStatus, void); | ||
|
|
||
|
|
@@ -653,6 +654,19 @@ int mingw_open (const char *filename, int oflags, ...) | |
| else if (xutftowcs_path(wfilename, filename) < 0) | ||
| return -1; | ||
|
|
||
| /* | ||
| * When `symlink` exists and is a symbolic link pointing to a | ||
| * non-existing file, `_wopen(symlink, O_CREAT | O_EXCL)` would | ||
| * create that file. Not what we want: Linux would say `EEXIST` | ||
| * in that instance, which is therefore what Git expects. | ||
| */ | ||
| if (create && | ||
| GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata) && | ||
| (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { | ||
| errno = EEXIST; | ||
| return -1; | ||
| } | ||
|
|
||
| fd = open_fn(wfilename, oflags, mode); | ||
|
|
||
| /* | ||
|
|
@@ -903,20 +917,22 @@ static int has_valid_directory_prefix(wchar_t *wfilename) | |
| return 1; | ||
| } | ||
|
|
||
| /* We keep the do_lstat code in a separate function to avoid recursion. | ||
| * When a path ends with a slash, the stat will fail with ENOENT. In | ||
| * this case, we strip the trailing slashes and stat again. | ||
| * | ||
| * If follow is true then act like stat() and report on the link | ||
| * target. Otherwise report on the link itself. | ||
| */ | ||
| static int do_lstat(int follow, const char *file_name, struct stat *buf) | ||
| int mingw_lstat(const char *file_name, struct stat *buf) | ||
| { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the Git mailing list, Johannes Sixt wrote (reply to this): Am 17.12.25 um 15:08 schrieb Karsten Blees via GitGitGadget:
> From: Karsten Blees <blees@dcon.de>
>
> The Win32 API function `GetFileAttributes()` cannot handle paths with
> trailing dir separators. The current `mingw_stat()`/`mingw_lstat()`
> implementation calls `GetFileAttributes()` twice if the path has
> trailing slashes (first with the original path that was passed as
> function parameter, and and a second time with a path copy with trailing
> '/' removed).
A comment above do_lstat() mentions this procedure. This patch doesn't
change the comment, but it should.
-- Hannes
|
||
| WIN32_FILE_ATTRIBUTE_DATA fdata; | ||
| wchar_t wfilename[MAX_PATH]; | ||
| if (xutftowcs_path(wfilename, file_name) < 0) | ||
| int wlen = xutftowcs_path(wfilename, file_name); | ||
| if (wlen < 0) | ||
| return -1; | ||
|
|
||
| /* strip trailing '/', or GetFileAttributes will fail */ | ||
| while (wlen && is_dir_sep(wfilename[wlen - 1])) | ||
| wfilename[--wlen] = 0; | ||
| if (!wlen) { | ||
| errno = ENOENT; | ||
| return -1; | ||
| } | ||
|
|
||
| if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) { | ||
| buf->st_ino = 0; | ||
| buf->st_gid = 0; | ||
|
|
@@ -935,13 +951,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf) | |
| if (handle != INVALID_HANDLE_VALUE) { | ||
| if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && | ||
| (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) { | ||
| if (follow) { | ||
| char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; | ||
| buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE); | ||
| } else { | ||
| buf->st_mode = S_IFLNK; | ||
| } | ||
| buf->st_mode |= S_IREAD; | ||
| buf->st_mode = S_IFLNK | S_IREAD; | ||
| if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) | ||
| buf->st_mode |= S_IWRITE; | ||
| } | ||
|
|
@@ -976,39 +986,6 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf) | |
| return -1; | ||
| } | ||
|
|
||
| /* We provide our own lstat/fstat functions, since the provided | ||
| * lstat/fstat functions are so slow. These stat functions are | ||
| * tailored for Git's usage (read: fast), and are not meant to be | ||
| * complete. Note that Git stat()s are redirected to mingw_lstat() | ||
| * too, since Windows doesn't really handle symlinks that well. | ||
| */ | ||
| static int do_stat_internal(int follow, const char *file_name, struct stat *buf) | ||
| { | ||
| size_t namelen; | ||
| char alt_name[PATH_MAX]; | ||
|
|
||
| if (!do_lstat(follow, file_name, buf)) | ||
| return 0; | ||
|
|
||
| /* if file_name ended in a '/', Windows returned ENOENT; | ||
| * try again without trailing slashes | ||
| */ | ||
| if (errno != ENOENT) | ||
| return -1; | ||
|
|
||
| namelen = strlen(file_name); | ||
| if (namelen && file_name[namelen-1] != '/') | ||
| return -1; | ||
| while (namelen && file_name[namelen-1] == '/') | ||
| --namelen; | ||
| if (!namelen || namelen >= PATH_MAX) | ||
| return -1; | ||
|
|
||
| memcpy(alt_name, file_name, namelen); | ||
| alt_name[namelen] = 0; | ||
| return do_lstat(follow, alt_name, buf); | ||
| } | ||
|
|
||
| static int get_file_info_by_handle(HANDLE hnd, struct stat *buf) | ||
| { | ||
| BY_HANDLE_FILE_INFORMATION fdata; | ||
|
|
@@ -1032,13 +1009,25 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf) | |
| return 0; | ||
| } | ||
|
|
||
| int mingw_lstat(const char *file_name, struct stat *buf) | ||
| { | ||
| return do_stat_internal(0, file_name, buf); | ||
| } | ||
| int mingw_stat(const char *file_name, struct stat *buf) | ||
| { | ||
| return do_stat_internal(1, file_name, buf); | ||
| wchar_t wfile_name[MAX_PATH]; | ||
| HANDLE hnd; | ||
| int result; | ||
|
|
||
| /* open the file and let Windows resolve the links */ | ||
| if (xutftowcs_path(wfile_name, file_name) < 0) | ||
| return -1; | ||
| hnd = CreateFileW(wfile_name, 0, | ||
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, | ||
| OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); | ||
| if (hnd == INVALID_HANDLE_VALUE) { | ||
| errno = err_win_to_posix(GetLastError()); | ||
| return -1; | ||
| } | ||
| result = get_file_info_by_handle(hnd, buf); | ||
| CloseHandle(hnd); | ||
| return result; | ||
| } | ||
|
|
||
| int mingw_fstat(int fd, struct stat *buf) | ||
|
|
@@ -1225,18 +1214,16 @@ char *mingw_getcwd(char *pointer, int len) | |
| { | ||
| wchar_t cwd[MAX_PATH], wpointer[MAX_PATH]; | ||
| DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd); | ||
| HANDLE hnd; | ||
|
|
||
| if (!ret || ret >= ARRAY_SIZE(cwd)) { | ||
| errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError()); | ||
| return NULL; | ||
| } | ||
| ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer)); | ||
| if (!ret && GetLastError() == ERROR_ACCESS_DENIED) { | ||
| HANDLE hnd = CreateFileW(cwd, 0, | ||
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, | ||
| OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); | ||
| if (hnd == INVALID_HANDLE_VALUE) | ||
| return NULL; | ||
| hnd = CreateFileW(cwd, 0, | ||
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, | ||
| OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); | ||
| if (hnd != INVALID_HANDLE_VALUE) { | ||
| ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0); | ||
| CloseHandle(hnd); | ||
| if (!ret || ret >= ARRAY_SIZE(wpointer)) | ||
|
|
@@ -1245,13 +1232,11 @@ char *mingw_getcwd(char *pointer, int len) | |
| return NULL; | ||
| return pointer; | ||
| } | ||
| if (!ret || ret >= ARRAY_SIZE(wpointer)) | ||
| return NULL; | ||
| if (GetFileAttributesW(wpointer) == INVALID_FILE_ATTRIBUTES) { | ||
| if (GetFileAttributesW(cwd) == INVALID_FILE_ATTRIBUTES) { | ||
| errno = ENOENT; | ||
| return NULL; | ||
| } | ||
| if (xwcstoutf(pointer, wpointer, len) < 0) | ||
| if (xwcstoutf(pointer, cwd, len) < 0) | ||
| return NULL; | ||
| convert_slashes(pointer); | ||
| return pointer; | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the Git mailing list, Johannes Sixt wrote (reply to this):