Merge pull request #1897 from piscisaureus/symlink-attr

Specify symlink type in .gitattributes
This commit is contained in:
Johannes Schindelin
2018-10-31 15:08:16 +01:00
25 changed files with 838 additions and 247 deletions

View File

@@ -294,9 +294,9 @@ docdep_prereqs = \
cmd-list.made $(cmds_txt)
doc.dep : $(docdep_prereqs) $(wildcard *.txt) $(wildcard config/*.txt) build-docdep.perl
$(QUIET_GEN)$(RM) $@+ $@ && \
$(PERL_PATH) ./build-docdep.perl >$@+ $(QUIET_STDERR) && \
mv $@+ $@
$(QUIET_GEN)$(RM) $@.new $@ && \
$(PERL_PATH) ./build-docdep.perl >$@.new $(QUIET_STDERR) && \
mv $@.new $@
-include doc.dep
@@ -332,8 +332,8 @@ mergetools-list.made: ../git-mergetool--lib.sh $(wildcard ../mergetools/*)
date >$@
clean:
$(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7
$(RM) *.texi *.texi+ *.texi++ git.info gitman.info
$(RM) *.xml *.xml.new *.html *.html.new *.1 *.5 *.7
$(RM) *.texi *.texi.new *.texi.new.new git.info gitman.info
$(RM) *.pdf
$(RM) howto-index.txt howto/*.html doc.dep
$(RM) technical/*.html technical/api-index.txt
@@ -342,14 +342,14 @@ clean:
$(RM) manpage-base-url.xsl
$(MAN_HTML): %.html : %.txt asciidoc.conf
$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
$(TXT_TO_HTML) -d manpage -o $@+ $< && \
mv $@+ $@
$(QUIET_ASCIIDOC)$(RM) $@.new $@ && \
$(TXT_TO_HTML) -d manpage -o $@.new $< && \
mv $@.new $@
$(OBSOLETE_HTML): %.html : %.txto asciidoc.conf
$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
$(TXT_TO_HTML) -o $@+ $< && \
mv $@+ $@
$(QUIET_ASCIIDOC)$(RM) $@.new $@ && \
$(TXT_TO_HTML) -o $@.new $< && \
mv $@.new $@
manpage-base-url.xsl: manpage-base-url.xsl.in
$(QUIET_GEN)sed "s|@@MAN_BASE_URL@@|$(MAN_BASE_URL)|" $< > $@
@@ -359,14 +359,14 @@ manpage-base-url.xsl: manpage-base-url.xsl.in
$(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $<
%.xml : %.txt asciidoc.conf
$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
$(TXT_TO_XML) -d manpage -o $@+ $< && \
mv $@+ $@
$(QUIET_ASCIIDOC)$(RM) $@.new $@ && \
$(TXT_TO_XML) -d manpage -o $@.new $< && \
mv $@.new $@
user-manual.xml: user-manual.txt user-manual.conf
$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
$(TXT_TO_XML) -d book -o $@+ $< && \
mv $@+ $@
$(QUIET_ASCIIDOC)$(RM) $@.new $@ && \
$(TXT_TO_XML) -d book -o $@.new $< && \
mv $@.new $@
technical/api-index.txt: technical/api-index-skel.txt \
technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS))
@@ -383,46 +383,46 @@ XSLT = docbook.xsl
XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css
user-manual.html: user-manual.xml $(XSLT)
$(QUIET_XSLTPROC)$(RM) $@+ $@ && \
xsltproc $(XSLTOPTS) -o $@+ $(XSLT) $< && \
mv $@+ $@
$(QUIET_XSLTPROC)$(RM) $@.new $@ && \
xsltproc $(XSLTOPTS) -o $@.new $(XSLT) $< && \
mv $@.new $@
git.info: user-manual.texi
$(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi
user-manual.texi: user-manual.xml
$(QUIET_DB2TEXI)$(RM) $@+ $@ && \
$(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@++ && \
$(PERL_PATH) fix-texi.perl <$@++ >$@+ && \
rm $@++ && \
mv $@+ $@
$(QUIET_DB2TEXI)$(RM) $@.new $@ && \
$(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@.new.new && \
$(PERL_PATH) fix-texi.perl <$@.new.new >$@.new && \
rm $@.new.new && \
mv $@.new $@
user-manual.pdf: user-manual.xml
$(QUIET_DBLATEX)$(RM) $@+ $@ && \
$(DBLATEX) -o $@+ $(DBLATEX_COMMON) $< && \
mv $@+ $@
$(QUIET_DBLATEX)$(RM) $@.new $@ && \
$(DBLATEX) -o $@.new $(DBLATEX_COMMON) $< && \
mv $@.new $@
gitman.texi: $(MAN_XML) cat-texi.perl texi.xsl
$(QUIET_DB2TEXI)$(RM) $@+ $@ && \
($(foreach xml,$(sort $(MAN_XML)),xsltproc -o $(xml)+ texi.xsl $(xml) && \
$(DOCBOOK2X_TEXI) --encoding=UTF-8 --to-stdout $(xml)+ && \
rm $(xml)+ &&) true) > $@++ && \
$(PERL_PATH) cat-texi.perl $@ <$@++ >$@+ && \
rm $@++ && \
mv $@+ $@
$(QUIET_DB2TEXI)$(RM) $@.new $@ && \
($(foreach xml,$(sort $(MAN_XML)),xsltproc -o $(xml).new texi.xsl $(xml) && \
$(DOCBOOK2X_TEXI) --encoding=UTF-8 --to-stdout $(xml).new && \
rm $(xml).new &&) true) > $@.new.new && \
$(PERL_PATH) cat-texi.perl $@ <$@.new.new >$@.new && \
rm $@.new.new && \
mv $@.new $@
gitman.info: gitman.texi
$(QUIET_MAKEINFO)$(MAKEINFO) --no-split --no-validate $*.texi
$(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
$(QUIET_DB2TEXI)$(RM) $@+ $@ && \
$(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+ && \
mv $@+ $@
$(QUIET_DB2TEXI)$(RM) $@.new $@ && \
$(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@.new && \
mv $@.new $@
howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
$(QUIET_GEN)$(RM) $@+ $@ && \
'$(SHELL_PATH_SQ)' ./howto-index.sh $(sort $(wildcard howto/*.txt)) >$@+ && \
mv $@+ $@
$(QUIET_GEN)$(RM) $@.new $@ && \
'$(SHELL_PATH_SQ)' ./howto-index.sh $(sort $(wildcard howto/*.txt)) >$@.new && \
mv $@.new $@
$(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
$(QUIET_ASCIIDOC)$(TXT_TO_HTML) $*.txt
@@ -431,10 +431,10 @@ WEBDOC_DEST = /pub/software/scm/git/docs
howto/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../
$(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
$(QUIET_ASCIIDOC)$(RM) $@.new $@ && \
sed -e '1,/^$$/d' $< | \
$(TXT_TO_HTML) - >$@+ && \
mv $@+ $@
$(TXT_TO_HTML) - >$@.new && \
mv $@.new $@
install-webdoc : html
'$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST)

View File

@@ -382,6 +382,36 @@ sign `$` upon checkout. Any byte sequence that begins with
with `$Id$` upon check-in.
`symlink`
^^^^^^^^^
On Windows, symbolic links have a type: a "file symlink" must point at
a file, and a "directory symlink" must point at a directory. If the
type of symlink does not match its target, it doesn't work.
Git does not record the type of symlink in the index or in a tree. On
checkout it'll guess the type, which only works if the target exists
at the time the symlink is created. This may often not be the case,
for example when the link points at a directory inside a submodule.
The `symlink` attribute allows you to explicitly set the type of symlink
to `file` or `dir`, so Git doesn't have to guess. If you have a set of
symlinks that point at other files, you can do:
------------------------
*.gif symlink=file
------------------------
To tell Git that a symlink points at a directory, use:
------------------------
tools_folder symlink=dir
------------------------
The `symlink` attribute is ignored on platforms other than Windows,
since they don't distinguish between different types of symlinks.
`filter`
^^^^^^^^

View File

@@ -578,6 +578,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
clean_get_color(CLEAN_COLOR_RESET));
}
fflush(stdout);
if (strbuf_getline_lf(&choice, stdin) != EOF) {
strbuf_trim(&choice);
} else {
@@ -660,6 +661,7 @@ static int filter_by_patterns_cmd(void)
clean_print_color(CLEAN_COLOR_PROMPT);
printf(_("Input ignore patterns>> "));
clean_print_color(CLEAN_COLOR_RESET);
fflush(stdout);
if (strbuf_getline_lf(&confirm, stdin) != EOF)
strbuf_trim(&confirm);
else
@@ -758,6 +760,7 @@ static int ask_each_cmd(void)
qname = quote_path_relative(item->string, NULL, &buf);
/* TRANSLATORS: Make sure to keep [y/N] as is */
printf(_("Remove %s [y/N]? "), qname);
fflush(stdout);
if (strbuf_getline_lf(&confirm, stdin) != EOF) {
strbuf_trim(&confirm);
} else {

View File

@@ -2,17 +2,17 @@
#include "win32.h"
#include <conio.h>
#include <wchar.h>
#include <winioctl.h>
#include "../strbuf.h"
#include "../run-command.h"
#include "../cache.h"
#include "win32/lazyload.h"
#include "../config.h"
#include "dir.h"
#include "../attr.h"
#define HCAST(type, handle) ((type)(intptr_t)handle)
static const int delay[] = { 0, 1, 10, 20, 40 };
void open_in_gdb(void)
{
static struct child_process cp = CHILD_PROCESS_INIT;
@@ -88,6 +88,7 @@ int err_win_to_posix(DWORD winerr)
case ERROR_INVALID_PARAMETER: error = EINVAL; break;
case ERROR_INVALID_PASSWORD: error = EPERM; break;
case ERROR_INVALID_PRIMARY_GROUP: error = EINVAL; break;
case ERROR_INVALID_REPARSE_DATA: error = EINVAL; break;
case ERROR_INVALID_SIGNAL_NUMBER: error = EINVAL; break;
case ERROR_INVALID_TARGET_HANDLE: error = EIO; break;
case ERROR_INVALID_WORKSTATION: error = EACCES; break;
@@ -102,6 +103,7 @@ int err_win_to_posix(DWORD winerr)
case ERROR_NEGATIVE_SEEK: error = ESPIPE; break;
case ERROR_NOACCESS: error = EFAULT; break;
case ERROR_NONE_MAPPED: error = EINVAL; break;
case ERROR_NOT_A_REPARSE_POINT: error = EINVAL; break;
case ERROR_NOT_ENOUGH_MEMORY: error = ENOMEM; break;
case ERROR_NOT_READY: error = EAGAIN; break;
case ERROR_NOT_SAME_DEVICE: error = EXDEV; break;
@@ -122,6 +124,9 @@ int err_win_to_posix(DWORD winerr)
case ERROR_PIPE_NOT_CONNECTED: error = EPIPE; break;
case ERROR_PRIVILEGE_NOT_HELD: error = EACCES; break;
case ERROR_READ_FAULT: error = EIO; break;
case ERROR_REPARSE_ATTRIBUTE_CONFLICT: error = EINVAL; break;
case ERROR_REPARSE_TAG_INVALID: error = EINVAL; break;
case ERROR_REPARSE_TAG_MISMATCH: error = EINVAL; break;
case ERROR_SEEK: error = EIO; break;
case ERROR_SEEK_ON_DEVICE: error = ESPIPE; break;
case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break;
@@ -188,15 +193,12 @@ static int read_yes_no_answer(void)
return -1;
}
static int ask_yes_no_if_possible(const char *format, ...)
static int ask_yes_no_if_possible(const char *format, va_list args)
{
char question[4096];
const char *retry_hook[] = { NULL, NULL, NULL };
va_list args;
va_start(args, format);
vsnprintf(question, sizeof(question), format, args);
va_end(args);
if ((retry_hook[0] = mingw_getenv("GIT_ASK_YESNO"))) {
retry_hook[1] = question;
@@ -218,6 +220,31 @@ static int ask_yes_no_if_possible(const char *format, ...)
}
}
static int retry_ask_yes_no(int *tries, const char *format, ...)
{
static const int delay[] = { 0, 1, 10, 20, 40 };
va_list args;
int result, saved_errno = errno;
if ((*tries) < ARRAY_SIZE(delay)) {
/*
* We assume that some other process had the file open at the wrong
* moment and retry. In order to give the other process a higher
* chance to complete its operation, we give up our time slice now.
* If we have to retry again, we do sleep a bit.
*/
Sleep(delay[*tries]);
(*tries)++;
return 1;
}
va_start(args, format);
result = ask_yes_no_if_possible(format, args);
va_end(args);
errno = saved_errno;
return result;
}
/* Windows only */
enum hide_dotfiles_type {
HIDE_DOTFILES_FALSE = 0,
@@ -259,6 +286,182 @@ int mingw_core_config(const char *var, const char *value, void *cb)
return 0;
}
static DWORD symlink_file_flags = 0, symlink_directory_flags = 1;
enum phantom_symlink_result {
PHANTOM_SYMLINK_RETRY,
PHANTOM_SYMLINK_DONE,
PHANTOM_SYMLINK_DIRECTORY
};
static inline int is_wdir_sep(wchar_t wchar)
{
return wchar == L'/' || wchar == L'\\';
}
static const wchar_t *make_relative_to(const wchar_t *path,
const wchar_t *relative_to, wchar_t *out,
size_t size)
{
size_t i = wcslen(relative_to), len;
/* Is `path` already absolute? */
if (is_wdir_sep(path[0]) ||
(iswalpha(path[0]) && path[1] == L':' && is_wdir_sep(path[2])))
return path;
while (i > 0 && !is_wdir_sep(relative_to[i - 1]))
i--;
/* Is `relative_to` in the current directory? */
if (!i)
return path;
len = wcslen(path);
if (i + len + 1 > size) {
error("Could not make '%S' relative to '%S' (too large)",
path, relative_to);
return NULL;
}
memcpy(out, relative_to, i * sizeof(wchar_t));
wcscpy(out + i, path);
return out;
}
/*
* Changes a file symlink to a directory symlink if the target exists and is a
* directory.
*/
static enum phantom_symlink_result
process_phantom_symlink(const wchar_t *wtarget, const wchar_t *wlink)
{
HANDLE hnd;
BY_HANDLE_FILE_INFORMATION fdata;
wchar_t relative[MAX_LONG_PATH];
const wchar_t *rel;
/* check that wlink is still a file symlink */
if ((GetFileAttributesW(wlink)
& (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
!= FILE_ATTRIBUTE_REPARSE_POINT)
return PHANTOM_SYMLINK_DONE;
/* make it relative, if necessary */
rel = make_relative_to(wtarget, wlink, relative, ARRAY_SIZE(relative));
if (!rel)
return PHANTOM_SYMLINK_DONE;
/* let Windows resolve the link by opening it */
hnd = CreateFileW(rel, 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 PHANTOM_SYMLINK_RETRY;
}
if (!GetFileInformationByHandle(hnd, &fdata)) {
errno = err_win_to_posix(GetLastError());
CloseHandle(hnd);
return PHANTOM_SYMLINK_RETRY;
}
CloseHandle(hnd);
/* if target exists and is a file, we're done */
if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
return PHANTOM_SYMLINK_DONE;
/* otherwise recreate the symlink with directory flag */
if (DeleteFileW(wlink) &&
CreateSymbolicLinkW(wlink, wtarget, symlink_directory_flags))
return PHANTOM_SYMLINK_DIRECTORY;
errno = err_win_to_posix(GetLastError());
return PHANTOM_SYMLINK_RETRY;
}
/* keep track of newly created symlinks to non-existing targets */
struct phantom_symlink_info {
struct phantom_symlink_info *next;
wchar_t *wlink;
wchar_t *wtarget;
};
static struct phantom_symlink_info *phantom_symlinks = NULL;
static CRITICAL_SECTION phantom_symlinks_cs;
static void process_phantom_symlinks(void)
{
struct phantom_symlink_info *current, **psi;
EnterCriticalSection(&phantom_symlinks_cs);
/* process phantom symlinks list */
psi = &phantom_symlinks;
while ((current = *psi)) {
enum phantom_symlink_result result = process_phantom_symlink(
current->wtarget, current->wlink);
if (result == PHANTOM_SYMLINK_RETRY) {
psi = &current->next;
} else {
/* symlink was processed, remove from list */
*psi = current->next;
free(current);
/* if symlink was a directory, start over */
if (result == PHANTOM_SYMLINK_DIRECTORY)
psi = &phantom_symlinks;
}
}
LeaveCriticalSection(&phantom_symlinks_cs);
}
static int create_phantom_symlink(wchar_t *wtarget, wchar_t *wlink)
{
int len;
/* create file symlink */
if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags)) {
errno = err_win_to_posix(GetLastError());
return -1;
}
/* convert to directory symlink if target exists */
switch (process_phantom_symlink(wtarget, wlink)) {
case PHANTOM_SYMLINK_RETRY: {
/* if target doesn't exist, add to phantom symlinks list */
wchar_t wfullpath[MAX_LONG_PATH];
struct phantom_symlink_info *psi;
/* convert to absolute path to be independent of cwd */
len = GetFullPathNameW(wlink, MAX_LONG_PATH, wfullpath, NULL);
if (!len || len >= MAX_LONG_PATH) {
errno = err_win_to_posix(GetLastError());
return -1;
}
/* over-allocate and fill phantom_symlink_info structure */
psi = xmalloc(sizeof(struct phantom_symlink_info) +
sizeof(wchar_t) * (len + wcslen(wtarget) + 2));
psi->wlink = (wchar_t *)(psi + 1);
wcscpy(psi->wlink, wfullpath);
psi->wtarget = psi->wlink + len + 1;
wcscpy(psi->wtarget, wtarget);
EnterCriticalSection(&phantom_symlinks_cs);
psi->next = phantom_symlinks;
phantom_symlinks = psi;
LeaveCriticalSection(&phantom_symlinks_cs);
break;
}
case PHANTOM_SYMLINK_DIRECTORY:
/* if we created a dir symlink, process other phantom symlinks */
process_phantom_symlinks();
break;
default:
break;
}
return 0;
}
/* Normalizes NT paths as returned by some low-level APIs. */
static wchar_t *normalize_ntpath(wchar_t *wbuf)
{
@@ -286,31 +489,28 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)
int mingw_unlink(const char *pathname)
{
int ret, tries = 0;
int tries = 0;
wchar_t wpathname[MAX_LONG_PATH];
if (xutftowcs_long_path(wpathname, pathname) < 0)
return -1;
/* read-only files cannot be removed */
_wchmod(wpathname, 0666);
while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
do {
/* read-only files cannot be removed */
_wchmod(wpathname, 0666);
if (!_wunlink(wpathname))
return 0;
if (!is_file_in_use_error(GetLastError()))
break;
/*
* We assume that some other process had the source or
* destination file open at the wrong moment and retry.
* In order to give the other process a higher chance to
* complete its operation, we give up our time slice now.
* If we have to retry again, we do sleep a bit.
* _wunlink() / DeleteFileW() for directory symlinks fails with
* ERROR_ACCESS_DENIED (EACCES), so try _wrmdir() as well. This is the
* same error we get if a file is in use (already checked above).
*/
Sleep(delay[tries]);
tries++;
}
while (ret == -1 && is_file_in_use_error(GetLastError()) &&
ask_yes_no_if_possible("Unlink of file '%s' failed. "
"Should I try again?", pathname))
ret = _wunlink(wpathname);
return ret;
if (!_wrmdir(wpathname))
return 0;
} while (retry_ask_yes_no(&tries, "Unlink of file '%s' failed. "
"Should I try again?", pathname));
return -1;
}
static int is_dir_empty(const wchar_t *wpath)
@@ -337,12 +537,14 @@ static int is_dir_empty(const wchar_t *wpath)
int mingw_rmdir(const char *pathname)
{
int ret, tries = 0;
int tries = 0;
wchar_t wpathname[MAX_LONG_PATH];
if (xutftowcs_long_path(wpathname, pathname) < 0)
return -1;
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
do {
if (!_wrmdir(wpathname))
return 0;
if (!is_file_in_use_error(GetLastError()))
errno = err_win_to_posix(GetLastError());
if (errno != EACCES)
@@ -351,21 +553,9 @@ int mingw_rmdir(const char *pathname)
errno = ENOTEMPTY;
break;
}
/*
* We assume that some other process had the source or
* destination file open at the wrong moment and retry.
* In order to give the other process a higher chance to
* complete its operation, we give up our time slice now.
* If we have to retry again, we do sleep a bit.
*/
Sleep(delay[tries]);
tries++;
}
while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) &&
ask_yes_no_if_possible("Deletion of directory '%s' failed. "
"Should I try again?", pathname))
ret = _wrmdir(wpathname);
return ret;
} while (retry_ask_yes_no(&tries, "Deletion of directory '%s' failed. "
"Should I try again?", pathname));
return -1;
}
static inline int needs_hiding(const char *path)
@@ -421,6 +611,8 @@ int mingw_mkdir(const char *path, int mode)
return -1;
ret = _wmkdir(wpath);
if (!ret)
process_phantom_symlinks();
if (!ret && needs_hiding(path))
return set_hidden_flag(wpath, 1);
return ret;
@@ -659,7 +851,24 @@ int mingw_chdir(const char *dirname)
wchar_t wdirname[MAX_LONG_PATH];
if (xutftowcs_long_path(wdirname, dirname) < 0)
return -1;
result = _wchdir(wdirname);
if (has_symlinks) {
HANDLE hnd = CreateFileW(wdirname, 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;
}
if (!GetFinalPathNameByHandleW(hnd, wdirname, ARRAY_SIZE(wdirname), 0)) {
errno = err_win_to_posix(GetLastError());
CloseHandle(hnd);
return -1;
}
CloseHandle(hnd);
}
result = _wchdir(normalize_ntpath(wdirname));
current_directory_len = GetCurrentDirectoryW(0, NULL);
return result;
}
@@ -705,53 +914,46 @@ 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)
{
WIN32_FILE_ATTRIBUTE_DATA fdata;
WIN32_FIND_DATAW findbuf = { 0 };
wchar_t wfilename[MAX_LONG_PATH];
if (xutftowcs_long_path(wfilename, file_name) < 0)
int wlen = xutftowcs_long_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)) {
/* for reparse points, use FindFirstFile to get the reparse tag */
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
if (handle == INVALID_HANDLE_VALUE)
goto error;
FindClose(handle);
}
buf->st_ino = 0;
buf->st_gid = 0;
buf->st_uid = 0;
buf->st_nlink = 1;
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
buf->st_size = fdata.nFileSizeLow |
(((off_t)fdata.nFileSizeHigh)<<32);
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
findbuf.dwReserved0);
buf->st_size = S_ISLNK(buf->st_mode) ? MAX_LONG_PATH :
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
WIN32_FIND_DATAW findbuf;
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
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;
if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
buf->st_mode |= S_IWRITE;
}
FindClose(handle);
}
}
return 0;
}
error:
switch (GetLastError()) {
case ERROR_ACCESS_DENIED:
case ERROR_SHARING_VIOLATION:
@@ -778,39 +980,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)
{
int namelen;
char alt_name[MAX_LONG_PATH];
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 >= MAX_LONG_PATH)
return -1;
memcpy(alt_name, file_name, namelen);
alt_name[namelen] = 0;
return do_lstat(follow, alt_name, buf);
}
int (*lstat)(const char *file_name, struct stat *buf) = mingw_lstat;
static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
@@ -826,7 +995,7 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
buf->st_gid = 0;
buf->st_uid = 0;
buf->st_nlink = 1;
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes, 0);
buf->st_size = fdata.nFileSizeLow |
(((off_t)fdata.nFileSizeHigh)<<32);
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
@@ -836,13 +1005,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_LONG_PATH];
HANDLE hnd;
int result;
/* open the file and let Windows resolve the links */
if (xutftowcs_long_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)
@@ -1442,6 +1623,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
HANDLE cons;
const char *(*quote_arg)(const char *arg) =
is_msys2_sh(*argv) ? quote_arg_msys2 : quote_arg_msvc;
const char *strace_env;
do_unset_environment_variables();
@@ -1513,6 +1695,31 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
free(quoted);
}
strace_env = getenv("GIT_STRACE_COMMANDS");
if (strace_env) {
char *p = path_lookup("strace.exe", 1);
if (!p)
return error("strace not found!");
if (xutftowcs_path(wcmd, p) < 0) {
free(p);
return -1;
}
free(p);
if (!strcmp("1", strace_env) ||
!strcasecmp("yes", strace_env) ||
!strcasecmp("true", strace_env))
strbuf_insert(&args, 0, "strace ", 7);
else {
const char *quoted = quote_arg(strace_env);
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "strace -o %s ", quoted);
if (quoted != strace_env)
free((char *)quoted);
strbuf_insert(&args, 0, buf.buf, buf.len);
strbuf_release(&buf);
}
}
ALLOC_ARRAY(wargs, st_add(st_mult(2, args.len), 1));
xutftowcs(wargs, args.buf, 2 * args.len + 1);
strbuf_release(&args);
@@ -1966,27 +2173,29 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
#undef rename
int mingw_rename(const char *pold, const char *pnew)
{
DWORD attrs, gle;
DWORD attrs = INVALID_FILE_ATTRIBUTES, gle;
int tries = 0;
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
if (xutftowcs_long_path(wpold, pold) < 0 ||
xutftowcs_long_path(wpnew, pnew) < 0)
return -1;
/*
* Try native rename() first to get errno right.
* It is based on MoveFile(), which cannot overwrite existing files.
*/
if (!_wrename(wpold, wpnew))
return 0;
if (errno != EEXIST)
return -1;
repeat:
if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
if (MoveFileExW(wpold, wpnew,
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
return 0;
/* TODO: translate more errors */
gle = GetLastError();
if (gle == ERROR_ACCESS_DENIED &&
/* revert file attributes on failure */
if (attrs != INVALID_FILE_ATTRIBUTES)
SetFileAttributesW(wpnew, attrs);
if (!is_file_in_use_error(gle)) {
errno = err_win_to_posix(gle);
return -1;
}
if (attrs == INVALID_FILE_ATTRIBUTES &&
(attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
DWORD attrsold = GetFileAttributesW(wpold);
@@ -1998,28 +2207,10 @@ repeat:
return -1;
}
if ((attrs & FILE_ATTRIBUTE_READONLY) &&
SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
return 0;
gle = GetLastError();
/* revert file attributes on failure */
SetFileAttributesW(wpnew, attrs);
}
SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY))
goto repeat;
}
if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) {
/*
* We assume that some other process had the source or
* destination file open at the wrong moment and retry.
* In order to give the other process a higher chance to
* complete its operation, we give up our time slice now.
* If we have to retry again, we do sleep a bit.
*/
Sleep(delay[tries]);
tries++;
goto repeat;
}
if (gle == ERROR_ACCESS_DENIED &&
ask_yes_no_if_possible("Rename from '%s' to '%s' failed. "
if (retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
"Should I try again?", pold, pnew))
goto repeat;
@@ -2291,6 +2482,177 @@ int link(const char *oldpath, const char *newpath)
return 0;
}
enum symlink_type {
SYMLINK_TYPE_UNSPECIFIED = 0,
SYMLINK_TYPE_FILE,
SYMLINK_TYPE_DIRECTORY,
};
static enum symlink_type check_symlink_attr(const char *link)
{
static struct attr_check *check;
const char *value;
if (!check)
check = attr_check_initl("symlink", NULL);
git_check_attr(the_repository->index, link, check);
value = check->items[0].value;
if (value == NULL)
;
else if (!strcmp(value, "file"))
return SYMLINK_TYPE_FILE;
else if (!strcmp(value, "dir"))
return SYMLINK_TYPE_DIRECTORY;
return SYMLINK_TYPE_UNSPECIFIED;
}
int symlink(const char *target, const char *link)
{
wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
int len;
/* fail if symlinks are disabled or API is not supported (WinXP) */
if (!has_symlinks) {
errno = ENOSYS;
return -1;
}
if ((len = xutftowcs_long_path(wtarget, target)) < 0
|| xutftowcs_long_path(wlink, link) < 0)
return -1;
/* convert target dir separators to backslashes */
while (len--)
if (wtarget[len] == '/')
wtarget[len] = '\\';
switch (check_symlink_attr(link)) {
case SYMLINK_TYPE_UNSPECIFIED:
/* Create a phantom symlink: it is initially created as a file
* symlink, but may change to a directory symlink later if/when
* the target exists. */
return create_phantom_symlink(wtarget, wlink);
case SYMLINK_TYPE_FILE:
if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags))
break;
return 0;
case SYMLINK_TYPE_DIRECTORY:
if (!CreateSymbolicLinkW(wlink, wtarget,
symlink_directory_flags))
break;
/* There may be dangling phantom symlinks that point at this
* one, which should now morph into directory symlinks. */
process_phantom_symlinks();
return 0;
default:
BUG("unhandled symlink type");
}
/* CreateSymbolicLinkW failed. */
errno = err_win_to_posix(GetLastError());
return -1;
}
#ifndef _WINNT_H
/*
* The REPARSE_DATA_BUFFER structure is defined in the Windows DDK (in
* ntifs.h) and in MSYS1's winnt.h (which defines _WINNT_H). So define
* it ourselves if we are on MSYS2 (whose winnt.h defines _WINNT_).
*/
typedef struct _REPARSE_DATA_BUFFER {
DWORD ReparseTag;
WORD ReparseDataLength;
WORD Reserved;
#ifndef _MSC_VER
_ANONYMOUS_UNION
#endif
union {
struct {
WORD SubstituteNameOffset;
WORD SubstituteNameLength;
WORD PrintNameOffset;
WORD PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
WORD SubstituteNameOffset;
WORD SubstituteNameLength;
WORD PrintNameOffset;
WORD PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct {
BYTE DataBuffer[1];
} GenericReparseBuffer;
} DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
#endif
int readlink(const char *path, char *buf, size_t bufsiz)
{
HANDLE handle;
WCHAR wpath[MAX_LONG_PATH], *wbuf;
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
DWORD dummy;
char tmpbuf[MAX_LONG_PATH];
int len;
if (xutftowcs_long_path(wpath, path) < 0)
return -1;
/* read reparse point data */
handle = CreateFileW(wpath, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
if (handle == INVALID_HANDLE_VALUE) {
errno = err_win_to_posix(GetLastError());
return -1;
}
if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, b,
MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dummy, NULL)) {
errno = err_win_to_posix(GetLastError());
CloseHandle(handle);
return -1;
}
CloseHandle(handle);
/* get target path for symlinks or mount points (aka 'junctions') */
switch (b->ReparseTag) {
case IO_REPARSE_TAG_SYMLINK:
wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
*(WCHAR*) (((char*) wbuf)
+ b->SymbolicLinkReparseBuffer.SubstituteNameLength) = 0;
break;
case IO_REPARSE_TAG_MOUNT_POINT:
wbuf = (WCHAR*) (((char*) b->MountPointReparseBuffer.PathBuffer)
+ b->MountPointReparseBuffer.SubstituteNameOffset);
*(WCHAR*) (((char*) wbuf)
+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
break;
default:
errno = EINVAL;
return -1;
}
/*
* Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
* cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
* condition. There is no conversion function that produces invalid UTF-8,
* so convert to a (hopefully large enough) temporary buffer, then memcpy
* the requested number of bytes (including '\0' for robustness).
*/
if ((len = xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
return -1;
memcpy(buf, tmpbuf, min(bufsiz, len + 1));
return min(bufsiz, len);
}
pid_t waitpid(pid_t pid, int *status, int options)
{
HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
@@ -2455,6 +2817,42 @@ static void setup_windows_environment(void)
/* simulate TERM to enable auto-color (see color.c) */
if (!getenv("TERM"))
setenv("TERM", "cygwin", 1);
/* calculate HOME if not set */
if (!getenv("HOME")) {
/*
* try $HOMEDRIVE$HOMEPATH - the home share may be a network
* location, thus also check if the path exists (i.e. is not
* disconnected)
*/
if ((tmp = getenv("HOMEDRIVE"))) {
struct strbuf buf = STRBUF_INIT;
strbuf_addstr(&buf, tmp);
if ((tmp = getenv("HOMEPATH"))) {
strbuf_addstr(&buf, tmp);
if (is_directory(buf.buf))
setenv("HOME", buf.buf, 1);
else
tmp = NULL; /* use $USERPROFILE */
}
strbuf_release(&buf);
}
/* use $USERPROFILE if the home share is not available */
if (!tmp && (tmp = getenv("USERPROFILE")))
setenv("HOME", tmp, 1);
}
/*
* Change 'core.symlinks' default to false, unless native symlinks are
* enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can
* run the test suite (which doesn't obey config files) with or without
* symlink support.
*/
if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict"))
has_symlinks = 0;
if (!getenv("LC_ALL") && !getenv("LC_CTYPE") && !getenv("LANG"))
setenv("LC_CTYPE", "C", 1);
}
int handle_long_path(wchar_t *path, int len, int max_path, int expand)
@@ -2603,6 +3001,24 @@ static void maybe_redirect_std_handles(void)
GENERIC_WRITE, FILE_FLAG_NO_BUFFERING);
}
static void adjust_symlink_flags(void)
{
/*
* Starting with Windows 10 Build 14972, symbolic links can be created
* using CreateSymbolicLink() without elevation by passing the flag
* SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE (0x02) as last
* parameter, provided the Developer Mode has been enabled. Some
* earlier Windows versions complain about this flag with an
* ERROR_INVALID_PARAMETER, hence we have to test the build number
* specifically.
*/
if (GetVersion() >= 14972 << 16) {
symlink_file_flags |= 2;
symlink_directory_flags |= 2;
}
}
#ifdef _MSC_VER
#ifdef _DEBUG
#include <crtdbg.h>
@@ -2635,6 +3051,7 @@ int wmain(int argc, const wchar_t **wargv)
#endif
maybe_redirect_std_handles();
adjust_symlink_flags();
/* determine size of argv and environ conversion buffer */
maxlen = wcslen(wargv[0]);
@@ -2664,6 +3081,7 @@ int wmain(int argc, const wchar_t **wargv)
/* initialize critical section for waitpid pinfo_t list */
InitializeCriticalSection(&pinfo_cs);
InitializeCriticalSection(&phantom_symlinks_cs);
/* set up default file mode and file modes for stdin/out/err */
_fmode = _O_BINARY;

View File

@@ -142,10 +142,6 @@ struct utsname {
* trivial stubs
*/
static inline int readlink(const char *path, char *buf, size_t bufsiz)
{ errno = ENOSYS; return -1; }
static inline int symlink(const char *oldpath, const char *newpath)
{ errno = ENOSYS; return -1; }
static inline int fchmod(int fildes, mode_t mode)
{ errno = ENOSYS; return -1; }
#ifndef __MINGW64_VERSION_MAJOR
@@ -237,6 +233,8 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out);
int sigaction(int sig, struct sigaction *in, struct sigaction *out);
int link(const char *oldpath, const char *newpath);
int uname(struct utsname *buf);
int symlink(const char *target, const char *link);
int readlink(const char *path, char *buf, size_t bufsiz);
/*
* replacements of existing functions

View File

@@ -1,7 +1,12 @@
#ifndef NO_INTTYPES_H
#include <inttypes.h>
#endif
#include "git-compat-util.h"
#include "run-command.h"
#include "compat/terminal.h"
#include "sigchain.h"
#include "strbuf.h"
#include "cache.h"
#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
@@ -91,6 +96,55 @@ static int disable_echo(void)
return 0;
}
static char *shell_prompt(const char *prompt, int echo)
{
const char *read_input[] = {
/* Note: call 'bash' explicitly, as 'read -s' is bash-specific */
"bash", "-c", echo ?
"cat >/dev/tty && read -r line </dev/tty && echo \"$line\"" :
"cat >/dev/tty && read -r -s line </dev/tty && echo \"$line\" && echo >/dev/tty",
NULL
};
struct child_process child = CHILD_PROCESS_INIT;
static struct strbuf buffer = STRBUF_INIT;
int prompt_len = strlen(prompt), len = -1, code;
child.argv = read_input;
child.in = -1;
child.out = -1;
child.silent_exec_failure = 1;
if (start_command(&child))
return NULL;
if (write_in_full(child.in, prompt, prompt_len) != prompt_len) {
error("could not write to prompt script");
close(child.in);
goto ret;
}
close(child.in);
strbuf_reset(&buffer);
len = strbuf_read(&buffer, child.out, 1024);
if (len < 0) {
error("could not read from prompt script");
goto ret;
}
strbuf_strip_suffix(&buffer, "\n");
strbuf_strip_suffix(&buffer, "\r");
ret:
close(child.out);
code = finish_command(&child);
if (code) {
error("failed to execute prompt script (exit code %d)", code);
return NULL;
}
return len < 0 ? NULL : buffer.buf;
}
#endif
#ifndef FORCE_TEXT
@@ -103,6 +157,15 @@ char *git_terminal_prompt(const char *prompt, int echo)
int r;
FILE *input_fh, *output_fh;
#ifdef GIT_WINDOWS_NATIVE
/* try shell_prompt first, fall back to CONIN/OUT if bash is missing */
char *result = shell_prompt(prompt, echo);
if (result || errno != ENOENT)
return result;
#endif
input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT);
if (!input_fh)
return NULL;

View File

@@ -6,10 +6,12 @@
#include <windows.h>
#endif
static inline int file_attr_to_st_mode (DWORD attr)
static inline int file_attr_to_st_mode (DWORD attr, DWORD tag)
{
int fMode = S_IREAD;
if (attr & FILE_ATTRIBUTE_DIRECTORY)
if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) && tag == IO_REPARSE_TAG_SYMLINK)
fMode |= S_IFLNK;
else if (attr & FILE_ATTRIBUTE_DIRECTORY)
fMode |= S_IFDIR;
else
fMode |= S_IFREG;

View File

@@ -16,7 +16,10 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
xwcstoutf(ent->d_name, fdata->cFileName, MAX_PATH * 3);
/* Set file type, based on WIN32_FIND_DATA */
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
if ((fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
&& fdata->dwReserved0 == IO_REPARSE_TAG_SYMLINK)
ent->d_type = DT_LNK;
else if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
ent->d_type = DT_DIR;
else
ent->d_type = DT_REG;

View File

@@ -149,9 +149,10 @@ static struct fsentry *fseentry_create_entry(struct fsentry *list,
fse = fsentry_alloc(list, buf, len);
fse->st_mode = file_attr_to_st_mode(fdata->dwFileAttributes);
fse->st_size = (((off64_t) (fdata->nFileSizeHigh)) << 32)
| fdata->nFileSizeLow;
fse->st_mode = file_attr_to_st_mode(fdata->dwFileAttributes,
fdata->dwReserved0);
fse->st_size = S_ISLNK(fse->st_mode) ? MAX_LONG_PATH :
fdata->nFileSizeLow | (((off_t) fdata->nFileSizeHigh) << 32);
filetime_to_timespec(&(fdata->ftLastAccessTime), &(fse->st_atim));
filetime_to_timespec(&(fdata->ftLastWriteTime), &(fse->st_mtim));
filetime_to_timespec(&(fdata->ftCreationTime), &(fse->st_ctim));
@@ -442,7 +443,8 @@ static struct dirent *fscache_readdir(DIR *base_dir)
if (!next)
return NULL;
dir->pfsentry = next;
dir->dirent.d_type = S_ISDIR(next->st_mode) ? DT_DIR : DT_REG;
dir->dirent.d_type = S_ISREG(next->st_mode) ? DT_REG :
S_ISDIR(next->st_mode) ? DT_DIR : DT_LNK;
dir->dirent.d_name = (char*) next->name;
return &(dir->dirent);
}

25
compat/win32/git.manifest Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="Git" version="0.0.0.1" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

View File

@@ -7,6 +7,7 @@
#include <wingdi.h>
#include <winreg.h>
#include "win32.h"
#include "win32/lazyload.h"
static int fd_is_interactive[3] = { 0, 0, 0 };
#define FD_CONSOLE 0x1
@@ -41,26 +42,21 @@ typedef struct _CONSOLE_FONT_INFOEX {
#endif
#endif
typedef BOOL (WINAPI *PGETCURRENTCONSOLEFONTEX)(HANDLE, BOOL,
PCONSOLE_FONT_INFOEX);
static void warn_if_raster_font(void)
{
DWORD fontFamily = 0;
PGETCURRENTCONSOLEFONTEX pGetCurrentConsoleFontEx;
DECLARE_PROC_ADDR(kernel32.dll, BOOL, GetCurrentConsoleFontEx,
HANDLE, BOOL, PCONSOLE_FONT_INFOEX);
/* don't bother if output was ascii only */
if (!non_ascii_used)
return;
/* GetCurrentConsoleFontEx is available since Vista */
pGetCurrentConsoleFontEx = (PGETCURRENTCONSOLEFONTEX) GetProcAddress(
GetModuleHandle("kernel32.dll"),
"GetCurrentConsoleFontEx");
if (pGetCurrentConsoleFontEx) {
if (INIT_PROC_ADDR(GetCurrentConsoleFontEx)) {
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof(cfi);
if (pGetCurrentConsoleFontEx(console, 0, &cfi))
if (GetCurrentConsoleFontEx(console, 0, &cfi))
fontFamily = cfi.FontFamily;
} else {
/* pre-Vista: check default console font in registry */

View File

@@ -586,7 +586,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
NO_STRTOUMAX = YesPlease
NO_MKDTEMP = YesPlease
NO_SVN_TESTS = YesPlease
NO_PERL_MAKEMAKER = YesPlease
RUNTIME_PREFIX = YesPlease
HAVE_WPGMPTR = YesWeDo
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
@@ -615,7 +614,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
NATIVE_CRLF = YesPlease
X = .exe
ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
htmldir = doc/git/html/
htmldir = share/doc/git/$(firstword $(subst -, ,$(GIT_VERSION)))/html
prefix =
INSTALL = /bin/install
EXTLIBS += /mingw/lib/libz.a
@@ -646,7 +645,8 @@ else
BASIC_LDFLAGS += -Wl,--large-address-aware
endif
CC = gcc
COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY
COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \
-fstack-protector-strong
EXTLIBS += -lntdll
INSTALL = /bin/install
NO_R_TO_GCC_LINKER = YesPlease
@@ -658,6 +658,7 @@ else
NO_LIBPCRE1_JIT = UnfortunatelyYes
NO_CURL =
USE_NED_ALLOCATOR = YesPlease
NO_PYTHON =
else
COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO
NO_CURL = YesPlease

4
diff.c
View File

@@ -4207,6 +4207,10 @@ static void run_external_diff(const char *pgm,
argv_array_pushf(&env, "GIT_DIFF_PATH_COUNTER=%d", ++o->diff_path_counter);
argv_array_pushf(&env, "GIT_DIFF_PATH_TOTAL=%d", q->nr);
if (one && one->should_munmap)
diff_free_filespec_data(one);
if (two && two->should_munmap)
diff_free_filespec_data(two);
if (run_command_v_opt_cd_env(argv.argv, RUN_USING_SHELL, NULL, env.argv))
die(_("external diff died, stopping at %s"), name);

View File

@@ -12,7 +12,9 @@
#ifndef NO_GETTEXT
# include <locale.h>
# include <libintl.h>
# ifdef HAVE_LIBCHARSET_H
# ifdef GIT_WINDOWS_NATIVE
# define locale_charset() "UTF-8"
# elif defined HAVE_LIBCHARSET_H
# include <libcharset.h>
# else
# include <langinfo.h>

2
git.rc
View File

@@ -20,3 +20,5 @@ BEGIN
VALUE "Translation", 0x409, 1200
END
END
1 RT_MANIFEST "compat/win32/git.manifest"

View File

@@ -293,11 +293,9 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
struct child_process gpg = CHILD_PROCESS_INIT;
int ret;
size_t i, j, bottom;
struct strbuf gpg_status = STRBUF_INIT;
argv_array_pushl(&gpg.args,
use_format->program,
"--status-fd=2",
"-bsau", signing_key,
NULL);
@@ -309,12 +307,10 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
*/
sigchain_push(SIGPIPE, SIG_IGN);
ret = pipe_command(&gpg, buffer->buf, buffer->len,
signature, 1024, &gpg_status, 0);
signature, 1024, NULL, 0);
sigchain_pop(SIGPIPE);
ret |= !strstr(gpg_status.buf, "\n[GNUPG:] SIG_CREATED ");
strbuf_release(&gpg_status);
if (ret)
if (ret || signature->len == bottom)
return error(_("gpg failed to sign the data"));
/* Strip CR from the line endings, in case we are on Windows. */

View File

@@ -17,14 +17,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);

View File

@@ -468,8 +468,6 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f)
}
#define STRBUF_MAXLINK (2*PATH_MAX)
int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
{
size_t oldalloc = sb->alloc;
@@ -477,15 +475,15 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
if (hint < 32)
hint = 32;
while (hint < STRBUF_MAXLINK) {
for (;;) {
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;
}

View File

@@ -412,7 +412,7 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '
# Tests for the hidden file attribute on windows
is_hidden () {
# Use the output of `attrib`, ignore the absolute path
case "$(attrib "$1")" in *H*?:*) return 0;; esac
case "$("$SYSTEMROOT"/system32/attrib "$1")" in *H*?:*) return 0;; esac
return 1
}

View File

@@ -0,0 +1,51 @@
#!/bin/sh
test_description='checkout symlinks with `symlink` attribute on Windows
Ensures that Git for Windows creates symlinks of the right type,
as specified by the `symlink` attribute in `.gitattributes`.'
# Tell MSYS to create native symlinks. Without this flag test-lib's
# prerequisite detection for SYMLINKS doesn't detect the right thing.
MSYS=winsymlinks:nativestrict && export MSYS
. ./test-lib.sh
if ! test_have_prereq MINGW,SYMLINKS
then
skip_all='skipping $0: MinGW-only test, which requires symlink support.'
test_done
fi
# Adds a symlink to the index without clobbering the work tree.
cache_symlink () {
sha=$(printf '%s' "$1" | git hash-object --stdin -w) &&
git update-index --add --cacheinfo 120000,$sha,"$2"
}
# MSYS2 is very forgiving, it will resolve symlinks even if the
# symlink type isn't correct. To make this test meaningful, try
# them with a native, non-MSYS executable.
cat_native () {
filename=$(cygpath -w "$1") &&
cmd.exe /c "type \"$filename\""
}
test_expect_success 'checkout symlinks with attr' '
cache_symlink file1 file-link &&
cache_symlink dir dir-link &&
printf "file-link symlink=file\ndir-link symlink=dir\n" >.gitattributes &&
git add .gitattributes &&
git checkout . &&
mkdir dir &&
echo "contents1" >file1 &&
echo "contents2" >dir/file2 &&
test "$(cat_native file-link)" = "contents1" &&
test "$(cat_native dir-link/file2)" = "contents2"
'
test_done

View File

@@ -95,7 +95,7 @@ test_expect_success 'clone -c remote.<remote>.fetch=<refspec> --origin=<name>' '
# Tests for the hidden file attribute on windows
is_hidden () {
# Use the output of `attrib`, ignore the absolute path
case "$(attrib "$1")" in *H*?:*) return 0;; esac
case "$("$SYSTEMROOT"/system32/attrib "$1")" in *H*?:*) return 0;; esac
return 1
}

View File

@@ -1345,12 +1345,6 @@ test_expect_success GPG \
'test_config user.signingkey BobTheMouse &&
test_must_fail git tag -s -m tail tag-gpg-failure'
# try to produce invalid signature
test_expect_success GPG \
'git tag -s fails if gpg is misconfigured (bad signature format)' \
'test_config gpg.program echo &&
test_must_fail git tag -s -m tail tag-gpg-failure'
# try to sign with bad user.signingkey
test_expect_success GPGSM \
'git tag -s fails if gpgsm is misconfigured (bad key)' \
@@ -1358,13 +1352,6 @@ test_expect_success GPGSM \
test_config gpg.format x509 &&
test_must_fail git tag -s -m tail tag-gpg-failure'
# try to produce invalid signature
test_expect_success GPGSM \
'git tag -s fails if gpgsm is misconfigured (bad signature format)' \
'test_config gpg.x509.program echo &&
test_config gpg.format x509 &&
test_must_fail git tag -s -m tail tag-gpg-failure'
# try to verify without gpg:
rm -rf gpghome

View File

@@ -1180,8 +1180,8 @@ test_expect_success $PREREQ 'in-reply-to but no threading' '
--to=nobody@example.com \
--in-reply-to="<in-reply-id@example.com>" \
--no-thread \
$patches |
grep "In-Reply-To: <in-reply-id@example.com>"
$patches >out &&
grep "In-Reply-To: <in-reply-id@example.com>" out
'
test_expect_success $PREREQ 'no in-reply-to and no threading' '

View File

@@ -12,6 +12,12 @@ then
test_done
fi
if test_have_prereq MINGW
then
skip_all='skipping remote-svn tests for lack of POSIX'
test_done
fi
# Override svnrdump with our simulator
PATH="$HOME:$PATH"
export PATH PYTHON_PATH GIT_BUILD_DIR

View File

@@ -43,14 +43,18 @@ test_expect_success 'setup repository and import' '
test_expect_success 'run log' "
git reset --hard origin/a &&
git svn log -r2 origin/trunk | grep ^r2 &&
git svn log -r4 origin/trunk | grep ^r4 &&
git svn log -r3 | grep ^r3
git svn log -r2 origin/trunk >out &&
grep ^r2 out &&
git svn log -r4 origin/trunk >out &&
grep ^r4 out &&
git svn log -r3 >out &&
grep ^r3 out
"
test_expect_success 'run log against a from trunk' "
git reset --hard origin/trunk &&
git svn log -r3 origin/a | grep ^r3
git svn log -r3 origin/a >out &&
grep ^r3 out
"
printf 'r1 \nr2 \nr4 \n' > expected-range-r1-r2-r4