summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2026-01-09 18:32:29 -0800
committerJunio C Hamano <gitster@pobox.com>2026-01-09 18:32:29 -0800
commitec13dca8d0619dc1cfe4cee5801b32bc58792ae2 (patch)
tree4c84be76afefce235e96f2cf18b40262f46a65e8
parentc4a0c8845e2426375ad257b6c221a3a7d92ecfda (diff)
parentaa7b8864d841f16044b0d79fce5baaec1830b3e3 (diff)
downloadgit-ec13dca8d0619dc1cfe4cee5801b32bc58792ae2.tar.gz
git-ec13dca8d0619dc1cfe4cee5801b32bc58792ae2.zip
Merge branch 'js/prep-symlink-windows' into js/symlink-windows
* js/prep-symlink-windows: trim_last_path_component(): avoid hard-coding the directory separator strbuf_readlink(): support link targets that exceed 2*PATH_MAX strbuf_readlink(): avoid calling `readlink()` twice in corner-cases init: do parse _all_ core.* settings early mingw: do resolve symlinks in `getcwd()` t7800: work around the MSYS path conversion on Windows t6423: introduce Windows-specific handling for symlinking to /dev/null t1305: skip symlink tests that do not apply to Windows t1006: accommodate for symlink support in MSYS2 t0600: fix incomplete prerequisite for a test case t0301: another fix for Windows compatibility t0001: handle `diff --no-index` gracefully mingw: special-case `open(symlink, O_CREAT | O_EXCL)` apply: symbolic links lack a "trustable executable bit" t9700: accommodate for Windows paths
-rw-r--r--apply.c2
-rw-r--r--compat/mingw.c32
-rw-r--r--environment.c4
-rw-r--r--environment.h2
-rw-r--r--lockfile.c4
-rw-r--r--setup.c2
-rw-r--r--strbuf.c8
-rwxr-xr-xt/t0001-init.sh6
-rwxr-xr-xt/t0301-credential-cache.sh3
-rwxr-xr-xt/t0600-reffiles-backend.sh2
-rwxr-xr-xt/t1006-cat-file.sh24
-rwxr-xr-xt/t1305-config-include.sh4
-rwxr-xr-xt/t6423-merge-rename-directories.sh9
-rwxr-xr-xt/t7800-difftool.sh8
-rwxr-xr-xt/t9700/test.pl9
15 files changed, 78 insertions, 41 deletions
diff --git a/apply.c b/apply.c
index c9fb45247d..3de4aa4d2e 100644
--- a/apply.c
+++ b/apply.c
@@ -3818,7 +3818,7 @@ static int check_preimage(struct apply_state *state,
if (*ce && !(*ce)->ce_mode)
BUG("ce_mode == 0 for path '%s'", old_name);
- if (trust_executable_bit)
+ if (trust_executable_bit || !S_ISREG(st->st_mode))
st_mode = ce_mode_from_stat(*ce, st->st_mode);
else if (*ce)
st_mode = (*ce)->ce_mode;
diff --git a/compat/mingw.c b/compat/mingw.c
index 939f938fe2..cf4f3c92e7 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -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);
/*
@@ -1225,18 +1239,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 +1257,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;
diff --git a/environment.c b/environment.c
index a770b5921d..b65b85a01f 100644
--- a/environment.c
+++ b/environment.c
@@ -324,8 +324,8 @@ next_name:
return (current & ~negative) | positive;
}
-static int git_default_core_config(const char *var, const char *value,
- const struct config_context *ctx, void *cb)
+int git_default_core_config(const char *var, const char *value,
+ const struct config_context *ctx, void *cb)
{
/* This needs a better name */
if (!strcmp(var, "core.filemode")) {
diff --git a/environment.h b/environment.h
index 51898c99cd..e61f843fdb 100644
--- a/environment.h
+++ b/environment.h
@@ -106,6 +106,8 @@ const char *strip_namespace(const char *namespaced_ref);
int git_default_config(const char *, const char *,
const struct config_context *, void *);
+int git_default_core_config(const char *var, const char *value,
+ const struct config_context *ctx, void *cb);
/*
* TODO: All the below state either explicitly or implicitly relies on
diff --git a/lockfile.c b/lockfile.c
index 1d5ed01682..67082a9caa 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -19,14 +19,14 @@ static void trim_last_path_component(struct strbuf *path)
int i = path->len;
/* back up past trailing slashes, if any */
- while (i && path->buf[i - 1] == '/')
+ while (i && is_dir_sep(path->buf[i - 1]))
i--;
/*
* then go backwards until a slash, or the beginning of the
* string
*/
- while (i && path->buf[i - 1] != '/')
+ while (i && !is_dir_sep(path->buf[i - 1]))
i--;
strbuf_setlen(path, i);
diff --git a/setup.c b/setup.c
index 3a6a048620..b723f8b339 100644
--- a/setup.c
+++ b/setup.c
@@ -2693,7 +2693,7 @@ int init_db(const char *git_dir, const char *real_git_dir,
* have set up the repository format such that we can evaluate
* includeIf conditions correctly in the case of re-initialization.
*/
- repo_config(the_repository, platform_core_config, NULL);
+ repo_config(the_repository, git_default_core_config, NULL);
safe_create_dir(the_repository, git_dir, 0);
diff --git a/strbuf.c b/strbuf.c
index 6c3851a7f8..ec2b7afbe6 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -566,7 +566,7 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f)
return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0;
}
-#define STRBUF_MAXLINK (2*PATH_MAX)
+#define STRBUF_MAXLINK (32767)
int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
{
@@ -578,12 +578,12 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
while (hint < STRBUF_MAXLINK) {
ssize_t len;
- strbuf_grow(sb, hint);
- len = readlink(path, sb->buf, hint);
+ strbuf_grow(sb, hint + 1);
+ len = readlink(path, sb->buf, hint + 1);
if (len < 0) {
if (errno != ERANGE)
break;
- } else if (len < hint) {
+ } else if (len <= hint) {
strbuf_setlen(sb, len);
return 0;
}
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 618da080dc..e4d32bb4d2 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -425,7 +425,11 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '
git init --separate-git-dir ../realgitdir
) &&
echo "gitdir: $(pwd)/realgitdir" >expected &&
- test_cmp expected newdir/.git &&
+ case "$GIT_TEST_CMP" in
+ # `git diff --no-index` does not resolve symlinks
+ *--no-index*) cmp expected newdir/.git;;
+ *) test_cmp expected newdir/.git;;
+ esac &&
test_cmp expected newdir/here &&
test_path_is_dir realgitdir/refs
'
diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh
index dc30289f75..6f7cfd9e33 100755
--- a/t/t0301-credential-cache.sh
+++ b/t/t0301-credential-cache.sh
@@ -123,7 +123,8 @@ test_expect_success SYMLINKS 'use user socket if user directory is a symlink to
rmdir \"\$HOME/dir/\" &&
rm \"\$HOME/.git-credential-cache\"
" &&
- mkdir -p -m 700 "$HOME/dir/" &&
+ mkdir -p "$HOME/dir/" &&
+ chmod 700 "$HOME/dir/" &&
ln -s "$HOME/dir" "$HOME/.git-credential-cache" &&
check approve cache <<-\EOF &&
protocol=https
diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh
index b11126ed47..74bfa2e9ba 100755
--- a/t/t0600-reffiles-backend.sh
+++ b/t/t0600-reffiles-backend.sh
@@ -467,7 +467,7 @@ test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' '
esac
'
-test_expect_success SYMLINKS 'symref transaction supports symlinks' '
+test_expect_success SYMLINKS,!MINGW 'symref transaction supports symlinks' '
test_when_finished "git symbolic-ref -d TEST_SYMREF_HEAD" &&
git update-ref refs/heads/new @ &&
test_config core.prefersymlinkrefs true &&
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
index 1f61b666a7..0eee3bb878 100755
--- a/t/t1006-cat-file.sh
+++ b/t/t1006-cat-file.sh
@@ -1048,18 +1048,28 @@ test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-
echo .. >>expect &&
echo HEAD:dir/subdir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual &&
test_cmp expect actual &&
- echo symlink 3 >expect &&
- echo ../ >>expect &&
+ if test_have_prereq MINGW,SYMLINKS
+ then
+ test_write_lines "symlink 2" ..
+ else
+ test_write_lines "symlink 3" ../
+ fi >expect &&
echo HEAD:dir/subdir/out-of-repo-link-dir-trailing | git cat-file --batch-check --follow-symlinks >actual &&
test_cmp expect actual
'
test_expect_success 'git cat-file --batch-check --follow-symlinks works for symlinks with internal ..' '
- echo HEAD: | git cat-file --batch-check >expect &&
- echo HEAD:up-down | git cat-file --batch-check --follow-symlinks >actual &&
- test_cmp expect actual &&
- echo HEAD:up-down-trailing | git cat-file --batch-check --follow-symlinks >actual &&
- test_cmp expect actual &&
+ if test_have_prereq !MINGW
+ then
+ # The `up-down` and `up-down-trailing` symlinks are normalized
+ # in MSYS in `winsymlinks` mode and are therefore in a
+ # different shape than Git expects them.
+ echo HEAD: | git cat-file --batch-check >expect &&
+ echo HEAD:up-down | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual &&
+ echo HEAD:up-down-trailing | git cat-file --batch-check --follow-symlinks >actual &&
+ test_cmp expect actual
+ fi &&
echo HEAD:up-down-file | git cat-file --batch-check --follow-symlinks >actual &&
test_cmp found actual &&
echo symlink 7 >expect &&
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
index 8ff2b0c232..6e51f892f3 100755
--- a/t/t1305-config-include.sh
+++ b/t/t1305-config-include.sh
@@ -286,7 +286,7 @@ test_expect_success SYMLINKS 'conditional include, relative path with symlinks'
)
'
-test_expect_success SYMLINKS 'conditional include, gitdir matching symlink' '
+test_expect_success SYMLINKS,!MINGW 'conditional include, gitdir matching symlink' '
ln -s foo bar &&
(
cd bar &&
@@ -298,7 +298,7 @@ test_expect_success SYMLINKS 'conditional include, gitdir matching symlink' '
)
'
-test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icase' '
+test_expect_success SYMLINKS,!MINGW 'conditional include, gitdir matching symlink, icase' '
(
cd bar &&
echo "[includeIf \"gitdir/i:BAR/\"]path=bar8" >>.git/config &&
diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh
index 533ac85dc8..53535a8ebf 100755
--- a/t/t6423-merge-rename-directories.sh
+++ b/t/t6423-merge-rename-directories.sh
@@ -5158,13 +5158,18 @@ test_setup_12m () {
git switch B &&
git rm dir/subdir/file &&
mkdir dir &&
- ln -s /dev/null dir/subdir &&
+ if test_have_prereq MINGW
+ then
+ cmd //c 'mklink dir\subdir NUL'
+ else
+ ln -s /dev/null dir/subdir
+ fi &&
git add . &&
git commit -m "B"
)
}
-test_expect_success '12m: Change parent of renamed-dir to symlink on other side' '
+test_expect_success SYMLINKS '12m: Change parent of renamed-dir to symlink on other side' '
test_setup_12m &&
(
cd 12m &&
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
index 9b74db5563..bf0f67378d 100755
--- a/t/t7800-difftool.sh
+++ b/t/t7800-difftool.sh
@@ -752,11 +752,11 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' '
c
EOF
git difftool --symlinks --dir-diff --extcmd ls >output &&
- grep -v ^/ output >actual &&
+ grep -v ":\$" output >actual &&
test_cmp expect actual &&
git difftool --no-symlinks --dir-diff --extcmd ls >output &&
- grep -v ^/ output >actual &&
+ grep -v ":\$" output >actual &&
test_cmp expect actual &&
# The left side contains symlink "c" that points to "b"
@@ -786,11 +786,11 @@ test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' '
EOF
git difftool --symlinks --dir-diff --extcmd ls >output &&
- grep -v ^/ output >actual &&
+ grep -v ":\$" output >actual &&
test_cmp expect actual &&
git difftool --no-symlinks --dir-diff --extcmd ls >output &&
- grep -v ^/ output >actual &&
+ grep -v ":\$" output >actual &&
test_cmp expect actual
'
diff --git a/t/t9700/test.pl b/t/t9700/test.pl
index 58a9b328d5..570b0c5680 100755
--- a/t/t9700/test.pl
+++ b/t/t9700/test.pl
@@ -117,7 +117,12 @@ close TEMPFILE;
unlink $tmpfile;
# paths
-is($r->repo_path, $abs_repo_dir . "/.git", "repo_path");
+my $abs_git_dir = $abs_repo_dir . "/.git";
+if ($^O eq 'msys' or $^O eq 'cygwin') {
+ $abs_git_dir = `cygpath -am "$abs_repo_dir/.git"`;
+ $abs_git_dir =~ s/\r?\n?$//;
+}
+is($r->repo_path, $abs_git_dir, "repo_path");
is($r->wc_path, $abs_repo_dir . "/", "wc_path");
is($r->wc_subdir, "", "wc_subdir initial");
$r->wc_chdir("directory1");
@@ -127,7 +132,7 @@ is($r->config("test.string"), "value", "config after wc_chdir");
# Object generation in sub directory
chdir("directory2");
my $r2 = Git->repository();
-is($r2->repo_path, $abs_repo_dir . "/.git", "repo_path (2)");
+is($r2->repo_path, $abs_git_dir, "repo_path (2)");
is($r2->wc_path, $abs_repo_dir . "/", "wc_path (2)");
is($r2->wc_subdir, "directory2/", "wc_subdir initial (2)");