diff --git a/Documentation/config.txt b/Documentation/config.txt index 62a2e9cecf..c0727b7866 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -911,6 +911,25 @@ relatively high IO latencies. When enabled, Git will do the index comparison to the filesystem data in parallel, allowing overlapping IO's. Defaults to true. +core.fscache:: + Enable additional caching of file system data for some operations. ++ +Git for Windows uses this to bulk-read and cache lstat data of entire +directories (instead of doing lstat file by file). + +core.longpaths:: + Enable long path (> 260) support for builtin commands in Git for + Windows. This is disabled by default, as long paths are not supported + by Windows Explorer, cmd.exe and the Git for Windows tool chain + (msys, bash, tcl, perl...). Only enable this if you know what you're + doing and are prepared to live with a few quirks. + +core.unsetenvvars:: + EXPERIMENTAL, Windows-only: comma-separated list of environment + variables' names that need to be unset before spawning any other + process. Defaults to `PERL5LIB` to account for the fact that Git + for Windows insists on using its own Perl interpreter. + core.createObject:: You can set this to 'link', in which case a hardlink followed by a delete of the source are used to make sure that object creation @@ -2238,6 +2257,27 @@ http.sslCAPath:: with when fetching or pushing over HTTPS. Can be overridden by the `GIT_SSL_CAPATH` environment variable. +http.sslBackend:: + Name of the SSL backend to use (e.g. "openssl" or "schannel"). + This option is ignored if cURL lacks support for choosing the SSL + backend at runtime. + +http.schannelCheckRevoke:: + Used to enforce or disable certificate revocation checks in cURL + when http.sslBackend is set to "schannel". Defaults to `true` if + unset. Only necessary to disable this if Git consistently errors + and the message is about checking the revocation status of a + certificate. This option is ignored if cURL lacks support for + setting the relevant SSL option at runtime. + +http.schannelUseSSLCAInfo:: + As of cURL v7.60.0, the Secure Channel backend can use the + certificate bundle provided via `http.sslCAInfo`, but that would + override the Windows Certificate Store. Since this is not desirable + by default, Git will tell cURL not to use that bundle by default + when the `schannel` backend was configured via `http.sslBackend`, + unless `http.schannelUseSSLCAInfo` overrides this behavior. + http.pinnedpubkey:: Public key of the https service. It may either be the filename of a PEM or DER encoded public key file or a string starting with @@ -3756,3 +3796,9 @@ worktree.guessRemote:: such a branch exists, it is checked out and set as "upstream" for the new branch. If no such match can be found, it falls back to creating a new branch from the current HEAD. + +sendpack.sideband:: + Allows to disable the side-band-64k capability for send-pack even + when it is advertised by the server. Makes it possible to work + around a limitation in the git for windows implementation together + with the dump git protocol. Defaults to true. diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 1d697d9962..14312c6d32 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git reset' [-q] [] [--] ... 'git reset' (--patch | -p) [] [--] [...] +EXPERIMENTAL: 'git reset' [-q] [--stdin [-z]] [] 'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [] DESCRIPTION @@ -97,6 +98,15 @@ OPTIONS --quiet:: Be quiet, only report errors. +--stdin:: + EXPERIMENTAL: Instead of taking list of paths from the + command line, read list of paths from the standard input. + Paths are separated by LF (i.e. one path per line) by + default. + +-z:: + EXPERIMENTAL: Only meaningful with `--stdin`; paths are + separated with NUL character instead of LF. EXAMPLES -------- diff --git a/Makefile b/Makefile index 5a969f5830..59735fc8f8 100644 --- a/Makefile +++ b/Makefile @@ -2087,7 +2087,7 @@ $(SCRIPT_LIB) : % : %.sh GIT-SCRIPT-DEFINES $(QUIET_GEN)$(cmd_munge_script) && \ mv $@+ $@ -git.res: git.rc GIT-VERSION-FILE +git.res: git.rc GIT-VERSION-FILE GIT-PREFIX $(QUIET_RC)$(RC) \ $(join -DMAJOR= -DMINOR= -DMICRO= -DPATCHLEVEL=, $(wordlist 1, 4, \ $(shell echo $(GIT_VERSION) 0 0 0 0 | tr '.a-zA-Z-' ' '))) \ @@ -2567,6 +2567,7 @@ GIT-BUILD-OPTIONS: FORCE @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+ @echo PAGER_ENV=\''$(subst ','\'',$(subst ','\'',$(PAGER_ENV)))'\' >>$@+ @echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+ + @echo X=\'$(X)\' >>$@+ ifdef TEST_OUTPUT_DIRECTORY @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+ endif diff --git a/README.md b/README.md index f920a42fad..bf4780c22d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https:/dev.azure.com/git/git/_apis/build/status/test-git.git)](https://dev.azure.com/git/git/_build/latest?definitionId=2) + Git - fast, scalable, distributed revision control system ========================================================= diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000000..52251a3331 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,325 @@ +resources: +- repo: self + fetchDepth: 1 + +phases: +- phase: linux_clang + displayName: linux-clang + condition: succeeded() + queue: + name: Hosted Ubuntu 1604 + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + sudo apt-get update && + sudo rm /var/lib/apt/lists/lock && + sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2-bin && + + export CC=clang || exit 1 + + ci/install-dependencies.sh + ci/run-build-and-tests.sh || { + ci/print-test-failures.sh + exit 1 + } + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-build-and-tests.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'linux-clang' + platform: Linux + publishRunAttachments: false + condition: succeededOrFailed() + +- phase: linux_gcc + displayName: linux-gcc + condition: succeeded() + queue: + name: Hosted Ubuntu 1604 + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + sudo apt-get update && + sudo rm /var/lib/apt/lists/lock && + sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2-bin || exit 1 + + ci/install-dependencies.sh + ci/run-build-and-tests.sh || { + ci/print-test-failures.sh + exit 1 + } + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-build-and-tests.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'linux-gcc' + platform: Linux + publishRunAttachments: false + condition: succeededOrFailed() + +- phase: osx_clang + displayName: osx-clang + condition: succeeded() + queue: + name: Hosted macOS + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + export CC=clang + + ci/install-dependencies.sh + ci/run-build-and-tests.sh || { + ci/print-test-failures.sh + exit 1 + } + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-build-and-tests.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'osx-clang' + platform: macOS + publishRunAttachments: false + condition: succeededOrFailed() + +- phase: osx_gcc + displayName: osx-gcc + condition: succeeded() + queue: + name: Hosted macOS + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + ci/install-dependencies.sh + ci/run-build-and-tests.sh || { + ci/print-test-failures.sh + exit 1 + } + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-build-and-tests.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'osx-gcc' + platform: macOS + publishRunAttachments: false + condition: succeededOrFailed() + +- phase: gettext_poison + displayName: GETTEXT_POISON + condition: succeeded() + queue: + name: Hosted Ubuntu 1604 + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + sudo apt-get update && + sudo rm /var/lib/apt/lists/lock && + sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev && + + export jobname=GETTEXT_POISON || exit 1 + + ci/run-build-and-tests.sh || { + ci/print-test-failures.sh + exit 1 + } + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-build-and-tests.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'gettext-poison' + platform: Linux + publishRunAttachments: false + condition: succeededOrFailed() + +- phase: windows + displayName: Windows + condition: succeeded() + queue: + name: Hosted VS2017 + timeoutInMinutes: 240 + steps: + - powershell: | + # Helper to check the error level of the latest command (exit with error when appropriate) + function c() { if (!$?) { exit(1) } } + + if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { + net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no; c + cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\; c + } + + # Add build agent's MinGit to PATH + $env:PATH = $env:AGENT_HOMEDIRECTORY +"\externals\\git\cmd;" +$env:PATH + + # Helper to initialize (or update) a Git worktree + function init ($path, $url, $set_origin) { + if (Test-Path $path) { + cd $path; c + if (Test-Path .git) { + git init; c + } else { + git status + } + } else { + git init $path; c + cd $path; c + } + git config core.autocrlf false; c + git config core.untrackedCache true; c + if (($set_origin -ne 0) -and !(git config remote.origin.url)) { + git remote add origin $url; c + } + git fetch --depth=1 $url master; c + git reset --hard FETCH_HEAD; c + git clean -df; c + } + + # Initialize Git for Windows' SDK + $sdk_path = "$(Build.SourcesDirectory)\git-sdk-64" + init "$sdk_path" "https://dev.azure.com/git-for-windows/git-sdk-64/_git/git-sdk-64" 0 + init usr\src\build-extra https://github.com/git-for-windows/build-extra 1 + + cd "$(Build.SourcesDirectory)"; c + + $env:HOME = "$(Build.SourcesDirectory)" + $env:MSYSTEM = "MINGW64" + git-sdk-64\git-cmd --command=usr\\bin\\bash.exe -lc @" + . ci/lib.sh + + make -j10 DEVELOPER=1 NO_PERL=1 || exit 1 + NO_PERL=1 NO_SVN_TESTS=1 GIT_TEST_OPTS=\"--quiet --write-junit-xml\" time make -j15 -k DEVELOPER=1 test || { + NO_PERL=1 NO_SVN_TESTS=1 GIT_TEST_OPTS=\"-i -v -x\" make -k -C t failed; exit 1 + } + + save_good_tree + "@ + c + + if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { + cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" + } + displayName: 'build & test' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'windows' + platform: Windows + publishRunAttachments: false + condition: succeededOrFailed() + +- phase: linux32 + displayName: Linux32 + condition: succeeded() + queue: + name: Hosted Ubuntu 1604 + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + sudo apt-get update && + sudo rm /var/lib/apt/lists/lock && + sudo apt-get -y install \ + apt-transport-https \ + ca-certificates \ + curl \ + software-properties-common && + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - && + sudo add-apt-repository \ + "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) \ + stable" && + sudo apt-get update && + sudo apt-get -y install docker-ce && + + sudo AGENT_OS="$AGENT_OS" BUILD_BUILDNUMBER="$BUILD_BUILDNUMBER" BUILD_REPOSITORY_URI="$BUILD_REPOSITORY_URI" BUILD_SOURCEBRANCH="$BUILD_SOURCEBRANCH" BUILD_SOURCEVERSION="$BUILD_SOURCEVERSION" SYSTEM_PHASENAME="$SYSTEM_PHASENAME" SYSTEM_TASKDEFINITIONSURI="$SYSTEM_TASKDEFINITIONSURI" SYSTEM_TEAMPROJECT="$SYSTEM_TEAMPROJECT" CC=$CC MAKEFLAGS=-j3 bash -lxc ci/run-linux32-docker.sh || exit 1 + + sudo chmod a+r t/out/TEST-*.xml + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-linux32-docker.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'linux32' + platform: Linux + publishRunAttachments: false + condition: succeededOrFailed() + +- phase: static_analysis + displayName: StaticAnalysis + condition: succeeded() + queue: + name: Hosted Ubuntu 1604 + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + sudo apt-get update && + sudo rm /var/lib/apt/lists/lock && + sudo apt-get install -y coccinelle && + + export jobname=StaticAnalysis && + + ci/run-static-analysis.sh || exit 1 + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-static-analysis.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + +- phase: documentation + displayName: Documentation + condition: succeeded() + queue: + name: Hosted Ubuntu 1604 + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + sudo apt-get update && + sudo rm /var/lib/apt/lists/lock && + sudo apt-get install -y asciidoc xmlto asciidoctor && + + export ALREADY_HAVE_ASCIIDOCTOR=yes. && + export jobname=Documentation && + + ci/test-documentation.sh || exit 1 + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 + displayName: 'ci/test-documentation.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) diff --git a/builtin/add.c b/builtin/add.c index 9916498a29..72baa72bc4 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -239,7 +239,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) rev.diffopt.output_format = DIFF_FORMAT_PATCH; rev.diffopt.use_color = 0; rev.diffopt.flags.ignore_dirty_submodules = 1; - out = open(file, O_CREAT | O_WRONLY, 0666); + out = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (out < 0) die(_("Could not open '%s' for writing."), file); rev.diffopt.file = xfdopen(out, "w"); diff --git a/builtin/clone.c b/builtin/clone.c index fd2c3ef090..2c7a1d973a 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1166,7 +1166,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } if (!is_local && !complete_refs_before_fetch) - transport_fetch_refs(transport, mapped_refs); + if (transport_fetch_refs(transport, mapped_refs)) + die(_("could not fetch refs from %s"), + transport->url); remote_head = find_ref_by_name(refs, "HEAD"); remote_head_points_at = diff --git a/builtin/commit.c b/builtin/commit.c index 0d9828e29e..f6be863a0a 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1355,6 +1355,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) PATHSPEC_PREFER_FULL, prefix, argv); + enable_fscache(1); read_cache_preload(&s.pathspec); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 9bd8a14b57..d10c9686f1 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -874,9 +874,20 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) } } +static void handle_reset(const char *name, struct object *object) +{ + int mark = get_object_mark(object); + + if (mark) + printf("reset %s\nfrom :%d\n\n", name, + get_object_mark(object)); + else + printf("reset %s\nfrom %s\n\n", name, + oid_to_hex(&object->oid)); +} + static void handle_tags_and_duplicates(void) { - struct commit *commit; int i; for (i = extra_refs.nr - 1; i >= 0; i--) { @@ -890,9 +901,7 @@ static void handle_tags_and_duplicates(void) if (anonymize) name = anonymize_refname(name); /* create refs pointing to already seen commits */ - commit = (struct commit *)object; - printf("reset %s\nfrom :%d\n\n", name, - get_object_mark(&commit->object)); + handle_reset(name, object); show_progress(); break; } diff --git a/builtin/gc.c b/builtin/gc.c index 57069442b0..8a99ed2af2 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -642,8 +642,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix) report_garbage = report_pack_garbage; reprepare_packed_git(the_repository); - if (pack_garbage.nr > 0) + if (pack_garbage.nr > 0) { + close_all_packs(the_repository->objects); clean_pack_garbage(); + } if (gc_write_commit_graph) write_commit_graph_reachable(get_object_directory(), 0); diff --git a/builtin/init-db.c b/builtin/init-db.c index 12ddda7e7b..f7f7bab789 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -155,6 +155,9 @@ static int git_init_db_config(const char *k, const char *v, void *cb) if (!strcmp(k, "init.templatedir")) return git_config_pathname(&init_db_template_dir, k, v); + if (starts_with(k, "core.")) + return platform_core_config(k, v, cb); + return 0; } @@ -361,6 +364,9 @@ int init_db(const char *git_dir, const char *real_git_dir, } startup_info->have_repository = 1; + /* Just look for `init.templatedir` and `core.hidedotfiles` */ + git_config(git_init_db_config, NULL); + safe_create_dir(git_dir, 0); init_is_bare_repository = is_bare_repository(); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index d1144a8f7e..29d48f3867 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2299,7 +2299,6 @@ static void init_threaded_search(void) pthread_mutex_init(&cache_mutex, NULL); pthread_mutex_init(&progress_mutex, NULL); pthread_cond_init(&progress_cond, NULL); - pthread_mutex_init(&to_pack.lock, NULL); old_try_to_free_routine = set_try_to_free_routine(try_to_free_from_threads); } diff --git a/builtin/repack.c b/builtin/repack.c index d5886039cc..5fdd19c9c4 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -408,6 +408,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (!names.nr && !po_args.quiet) printf("Nothing new to pack.\n"); + close_all_packs(the_repository->objects); + /* * Ok we have prepared all new packfiles. * First see if there are packs of the same name and if so diff --git a/builtin/reset.c b/builtin/reset.c index 11cd0dcb8c..b79e840667 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -24,10 +24,13 @@ #include "cache-tree.h" #include "submodule.h" #include "submodule-config.h" +#include "strbuf.h" +#include "quote.h" static const char * const git_reset_usage[] = { N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] []"), N_("git reset [-q] [] [--] ..."), + N_("EXPERIMENTAL: git reset [-q] [--stdin [-z]] []"), N_("git reset --patch [] [--] [...]"), NULL }; @@ -280,7 +283,9 @@ static int git_reset_config(const char *var, const char *value, void *cb) int cmd_reset(int argc, const char **argv, const char *prefix) { int reset_type = NONE, update_ref_status = 0, quiet = 0; - int patch_mode = 0, unborn; + int patch_mode = 0, nul_term_line = 0, read_from_stdin = 0, unborn; + char **stdin_paths = NULL; + int stdin_nr = 0, stdin_alloc = 0; const char *rev; struct object_id oid; struct pathspec pathspec; @@ -302,6 +307,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix) OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")), OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that removed paths will be added later")), + OPT_BOOL('z', NULL, &nul_term_line, + N_("EXPERIMENTAL: paths are separated with NUL character")), + OPT_BOOL(0, "stdin", &read_from_stdin, + N_("EXPERIMENTAL: read paths from ")), OPT_END() }; @@ -311,6 +320,42 @@ int cmd_reset(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH); parse_args(&pathspec, argv, prefix, patch_mode, &rev); + if (read_from_stdin) { + strbuf_getline_fn getline_fn = nul_term_line ? + strbuf_getline_nul : strbuf_getline_lf; + int flags = PATHSPEC_PREFER_FULL; + struct strbuf buf = STRBUF_INIT; + struct strbuf unquoted = STRBUF_INIT; + + if (patch_mode) + die(_("--stdin is incompatible with --patch")); + + if (pathspec.nr) + die(_("--stdin is incompatible with path arguments")); + + while (getline_fn(&buf, stdin) != EOF) { + if (!nul_term_line && buf.buf[0] == '"') { + strbuf_reset(&unquoted); + if (unquote_c_style(&unquoted, buf.buf, NULL)) + die(_("line is badly quoted")); + strbuf_swap(&buf, &unquoted); + } + ALLOC_GROW(stdin_paths, stdin_nr + 1, stdin_alloc); + stdin_paths[stdin_nr++] = xstrdup(buf.buf); + strbuf_reset(&buf); + } + strbuf_release(&unquoted); + strbuf_release(&buf); + + ALLOC_GROW(stdin_paths, stdin_nr + 1, stdin_alloc); + stdin_paths[stdin_nr++] = NULL; + flags |= PATHSPEC_LITERAL_PATH; + parse_pathspec(&pathspec, 0, flags, prefix, + (const char **)stdin_paths); + + } else if (nul_term_line) + die(_("-z requires --stdin")); + unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid); if (unborn) { /* reset on unborn branch: treat as reset to empty tree */ @@ -401,5 +446,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (!pathspec.nr) remove_branch_state(); + if (stdin_paths) { + while (stdin_nr) + free(stdin_paths[--stdin_nr]); + free(stdin_paths); + } + return update_ref_status; } diff --git a/bundle.c b/bundle.c index 24cbe40986..874696e4e4 100644 --- a/bundle.c +++ b/bundle.c @@ -457,10 +457,11 @@ int create_bundle(struct bundle_header *header, const char *path, object_array_remove_duplicates(&revs.pending); ref_count = write_bundle_refs(bundle_fd, &revs); - if (!ref_count) - die(_("Refusing to create empty bundle.")); - else if (ref_count < 0) + if (ref_count <= 0) { + if (!ref_count) + error(_("Refusing to create empty bundle.")); goto err; + } /* write pack */ if (write_pack_data(bundle_fd, &revs)) { diff --git a/cache.h b/cache.h index 4d014541ab..f49d9fbac8 100644 --- a/cache.h +++ b/cache.h @@ -901,14 +901,6 @@ int use_optional_locks(void); extern char comment_line_char; extern int auto_comment_line_char; -/* Windows only */ -enum hide_dotfiles_type { - HIDE_DOTFILES_FALSE = 0, - HIDE_DOTFILES_TRUE, - HIDE_DOTFILES_DOTGITONLY -}; -extern enum hide_dotfiles_type hide_dotfiles; - enum log_refs_config { LOG_REFS_UNSET = -1, LOG_REFS_NONE = 0, diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index 75a9fd2475..63fa37f68f 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -3,7 +3,7 @@ # Install dependencies required to build and test Git on Linux and macOS # -. ${0%/*}/lib-travisci.sh +. ${0%/*}/lib.sh P4WHENCE=http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION LFSWHENCE=https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION @@ -28,7 +28,8 @@ osx-clang|osx-gcc) brew update --quiet # Uncomment this if you want to run perf tests: # brew install gnu-time - brew install git-lfs gettext + test -z "$BREW_INSTALL_PACKAGES" || + brew install $BREW_INSTALL_PACKAGES brew link --force gettext brew install caskroom/cask/perforce ;; diff --git a/ci/lib-travisci.sh b/ci/lib.sh similarity index 61% rename from ci/lib-travisci.sh rename to ci/lib.sh index 06970f7213..e1858ae609 100755 --- a/ci/lib-travisci.sh +++ b/ci/lib.sh @@ -1,5 +1,49 @@ # Library of functions shared by all CI scripts +if test true = "$TRAVIS" +then + # We are running within Travis CI + CI_BRANCH="${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH}" + CI_COMMIT="$TRAVIS_COMMIT" + CI_JOB_ID="$TRAVIS_JOB_ID" + CI_JOB_NUMBER="$TRAVIS_JOB_NUMBER" + CI_OS_NAME="$TRAVIS_OS_NAME" + CI_REPO_SLUG="$TRAVIS_REPO_SLUG" + + cache_dir="$HOME/travis-cache" + + url_for_job_id () { + echo "https://travis-ci.org/$CI_REPO_SLUG/jobs/$1" + } + + BREW_INSTALL_PACKAGES="git-lfs gettext" + export GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save" + export GIT_TEST_OPTS="--verbose-log -x --immediate" +elif test -n "$SYSTEM_TASKDEFINITIONSURI" +then + # We are running in Azure Pipelines + CI_BRANCH="$BUILD_SOURCEBRANCH" + CI_COMMIT="$BUILD_SOURCEVERSION" + CI_JOB_ID="$BUILD_BUILDID" + CI_JOB_NUMBER="$BUILD_BUILDNUMBER" + CI_OS_NAME="$(echo "$AGENT_OS" | tr A-Z a-z)" + test darwin != "$CI_OS_NAME" || CI_OS_NAME=osx + CI_REPO_SLUG="$(expr "$BUILD_REPOSITORY_URI" : '.*/\([^/]*/[^/]*\)$')" + CC="${CC:-gcc}" + + # use a subdirectory of the cache dir (because the file share is shared + # among *all* phases) + cache_dir="$HOME/test-cache/$SYSTEM_PHASENAME" + + url_for_job_id () { + echo "$SYSTEM_TASKDEFINITIONSURI$SYSTEM_TEAMPROJECT/_build/results?buildId=$1" + } + + BREW_INSTALL_PACKAGES= + export GIT_PROVE_OPTS="--timer --jobs 10 --state=failed,slow,save" + export GIT_TEST_OPTS="--quiet --write-junit-xml" +fi + skip_branch_tip_with_tag () { # Sometimes, a branch is pushed at the same time the tag that points # at the same commit as the tip of the branch is pushed, and building @@ -13,10 +57,10 @@ skip_branch_tip_with_tag () { # we can skip the build because we won't be skipping a build # of a tag. - if TAG=$(git describe --exact-match "$TRAVIS_BRANCH" 2>/dev/null) && - test "$TAG" != "$TRAVIS_BRANCH" + if TAG=$(git describe --exact-match "$CI_BRANCH" 2>/dev/null) && + test "$TAG" != "$CI_BRANCH" then - echo "$(tput setaf 2)Tip of $TRAVIS_BRANCH is exactly at $TAG$(tput sgr0)" + echo "$(tput setaf 2)Tip of $CI_BRANCH is exactly at $TAG$(tput sgr0)" exit 0 fi } @@ -25,7 +69,7 @@ skip_branch_tip_with_tag () { # job if we encounter the same tree again and can provide a useful info # message. save_good_tree () { - echo "$(git rev-parse $TRAVIS_COMMIT^{tree}) $TRAVIS_COMMIT $TRAVIS_JOB_NUMBER $TRAVIS_JOB_ID" >>"$good_trees_file" + echo "$(git rev-parse $CI_COMMIT^{tree}) $CI_COMMIT $CI_JOB_NUMBER $CI_JOB_ID" >>"$good_trees_file" # limit the file size tail -1000 "$good_trees_file" >"$good_trees_file".tmp mv "$good_trees_file".tmp "$good_trees_file" @@ -35,7 +79,7 @@ save_good_tree () { # successfully before (e.g. because the branch got rebased, changing only # the commit messages). skip_good_tree () { - if ! good_tree_info="$(grep "^$(git rev-parse $TRAVIS_COMMIT^{tree}) " "$good_trees_file")" + if ! good_tree_info="$(grep "^$(git rev-parse $CI_COMMIT^{tree}) " "$good_trees_file")" then # Haven't seen this tree yet, or no cached good trees file yet. # Continue the build job. @@ -45,18 +89,18 @@ skip_good_tree () { echo "$good_tree_info" | { read tree prev_good_commit prev_good_job_number prev_good_job_id - if test "$TRAVIS_JOB_ID" = "$prev_good_job_id" + if test "$CI_JOB_ID" = "$prev_good_job_id" then cat <<-EOF - $(tput setaf 2)Skipping build job for commit $TRAVIS_COMMIT.$(tput sgr0) + $(tput setaf 2)Skipping build job for commit $CI_COMMIT.$(tput sgr0) This commit has already been built and tested successfully by this build job. To force a re-build delete the branch's cache and then hit 'Restart job'. EOF else cat <<-EOF - $(tput setaf 2)Skipping build job for commit $TRAVIS_COMMIT.$(tput sgr0) + $(tput setaf 2)Skipping build job for commit $CI_COMMIT.$(tput sgr0) This commit's tree has already been built and tested successfully in build job $prev_good_job_number for commit $prev_good_commit. - The log of that build job is available at https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$prev_good_job_id + The log of that build job is available at $(url_for_job_id $prev_good_job_id) To force a re-build delete the branch's cache and then hit 'Restart job'. EOF fi @@ -81,7 +125,6 @@ check_unignored_build_artifacts () # and installing dependencies. set -ex -cache_dir="$HOME/travis-cache" good_trees_file="$cache_dir/good-trees" mkdir -p "$cache_dir" @@ -91,13 +134,11 @@ skip_good_tree if test -z "$jobname" then - jobname="$TRAVIS_OS_NAME-$CC" + jobname="$CI_OS_NAME-$CC" fi export DEVELOPER=1 export DEFAULT_TEST_TARGET=prove -export GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save" -export GIT_TEST_OPTS="--verbose-log -x --immediate" export GIT_TEST_CLONE_2GB=YesPlease if [ "$jobname" = linux-gcc ]; then export CC=gcc-8 diff --git a/ci/mount-fileshare.sh b/ci/mount-fileshare.sh new file mode 100755 index 0000000000..5fb5f74b70 --- /dev/null +++ b/ci/mount-fileshare.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +die () { + echo "$*" >&2 + exit 1 +} + +test $# = 4 || +die "Usage: $0 dwHighDateTime << 32) + ft->dwLowDateTime; - /* Windows to Unix Epoch conversion */ - return winTime - 116444736000000000LL; -} - -static inline time_t filetime_to_time_t(const FILETIME *ft) -{ - return (time_t)(filetime_to_hnsec(ft) / 10000000); -} - /** * Verifies that safe_create_leading_directories() would succeed. */ @@ -612,8 +647,8 @@ static int has_valid_directory_prefix(wchar_t *wfilename) static int do_lstat(int follow, const char *file_name, struct stat *buf) { WIN32_FILE_ATTRIBUTE_DATA fdata; - wchar_t wfilename[MAX_PATH]; - if (xutftowcs_path(wfilename, file_name) < 0) + wchar_t wfilename[MAX_LONG_PATH]; + if (xutftowcs_long_path(wfilename, file_name) < 0) return -1; if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) { @@ -684,7 +719,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf) static int do_stat_internal(int follow, const char *file_name, struct stat *buf) { int namelen; - char alt_name[PATH_MAX]; + char alt_name[MAX_LONG_PATH]; if (!do_lstat(follow, file_name, buf)) return 0; @@ -700,7 +735,7 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf) return -1; while (namelen && file_name[namelen-1] == '/') --namelen; - if (!namelen || namelen >= PATH_MAX) + if (!namelen || namelen >= MAX_LONG_PATH) return -1; memcpy(alt_name, file_name, namelen); @@ -708,6 +743,8 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf) return do_lstat(follow, alt_name, buf); } +int (*lstat)(const char *file_name, struct stat *buf) = mingw_lstat; + int mingw_lstat(const char *file_name, struct stat *buf) { return do_stat_internal(0, file_name, buf); @@ -760,8 +797,8 @@ int mingw_utime (const char *file_name, const struct utimbuf *times) FILETIME mft, aft; int fh, rc; DWORD attrs; - wchar_t wfilename[MAX_PATH]; - if (xutftowcs_path(wfilename, file_name) < 0) + wchar_t wfilename[MAX_LONG_PATH]; + if (xutftowcs_long_path(wfilename, file_name) < 0) return -1; /* must have write permission */ @@ -820,11 +857,20 @@ unsigned int sleep (unsigned int seconds) char *mingw_mktemp(char *template) { wchar_t wtemplate[MAX_PATH]; + int offset = 0; + + /* we need to return the path, thus no long paths here! */ if (xutftowcs_path(wtemplate, template) < 0) return NULL; + + if (is_dir_sep(template[0]) && !is_dir_sep(template[1]) && + iswalpha(wtemplate[0]) && wtemplate[1] == L':') { + /* We have an absolute path missing the drive prefix */ + offset = 2; + } if (!_wmktemp(wtemplate)) return NULL; - if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0) + if (xwcstoutf(template, wtemplate + offset, strlen(template) + 1) < 0) return NULL; return template; } @@ -889,8 +935,32 @@ struct tm *localtime_r(const time_t *timep, struct tm *result) char *mingw_getcwd(char *pointer, int len) { - wchar_t wpointer[MAX_PATH]; - if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer))) + wchar_t cwd[MAX_PATH], wpointer[MAX_PATH]; + DECLARE_PROC_ADDR(kernel32.dll, DWORD, GetFinalPathNameByHandleW, + HANDLE, LPWSTR, DWORD, DWORD); + DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd); + + 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 && + INIT_PROC_ADDR(GetFinalPathNameByHandleW)) { + 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; + ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0); + CloseHandle(hnd); + if (!ret || ret >= ARRAY_SIZE(wpointer)) + return NULL; + if (xwcstoutf(pointer, normalize_ntpath(wpointer), len) < 0) + return NULL; + return pointer; + } + if (!ret || ret >= ARRAY_SIZE(wpointer)) return NULL; if (xwcstoutf(pointer, wpointer, len) < 0) return NULL; @@ -1031,14 +1101,21 @@ static char *lookup_prog(const char *dir, int dirlen, const char *cmd, int isexe, int exe_only) { char path[MAX_PATH]; + wchar_t wpath[MAX_PATH]; snprintf(path, sizeof(path), "%.*s\\%s.exe", dirlen, dir, cmd); - if (!isexe && access(path, F_OK) == 0) + if (xutftowcs_path(wpath, path) < 0) + return NULL; + + if (!isexe && _waccess(wpath, F_OK) == 0) return xstrdup(path); - path[strlen(path)-4] = '\0'; - if ((!exe_only || isexe) && access(path, F_OK) == 0) - if (!(GetFileAttributes(path) & FILE_ATTRIBUTE_DIRECTORY)) + wpath[wcslen(wpath)-4] = '\0'; + if ((!exe_only || isexe) && _waccess(wpath, F_OK) == 0) { + if (!(GetFileAttributesW(wpath) & FILE_ATTRIBUTE_DIRECTORY)) { + path[strlen(path)-4] = '\0'; return xstrdup(path); + } + } return NULL; } @@ -1175,6 +1252,27 @@ static wchar_t *make_environment_block(char **deltaenv) return wenvblk; } +static void do_unset_environment_variables(void) +{ + static int done; + char *p = unset_environment_variables; + + if (done || !p) + return; + done = 1; + + for (;;) { + char *comma = strchr(p, ','); + + if (comma) + *comma = '\0'; + unsetenv(p); + if (!comma) + break; + p = comma + 1; + } +} + struct pinfo_t { struct pinfo_t *next; pid_t pid; @@ -1215,17 +1313,25 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen const char *dir, const char *prepend_cmd, int fhin, int fhout, int fherr) { - STARTUPINFOW si; + static int restrict_handle_inheritance = 1; + STARTUPINFOEXW si; PROCESS_INFORMATION pi; + LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL; + HANDLE stdhandles[3]; + DWORD stdhandles_count = 0; + SIZE_T size; struct strbuf args; wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL; unsigned flags = CREATE_UNICODE_ENVIRONMENT; BOOL ret; + HANDLE cons; const char *(*quote_arg)(const char *arg) = is_msys2_sh(*argv) ? quote_arg_msys2 : quote_arg_msvc; + do_unset_environment_variables(); + /* Determine whether or not we are associated to a console */ - HANDLE cons = CreateFile("CONOUT$", GENERIC_WRITE, + cons = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (cons == INVALID_HANDLE_VALUE) { @@ -1249,13 +1355,28 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen CloseHandle(cons); } memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = winansi_get_osfhandle(fhin); - si.hStdOutput = winansi_get_osfhandle(fhout); - si.hStdError = winansi_get_osfhandle(fherr); + si.StartupInfo.cb = sizeof(si); + si.StartupInfo.hStdInput = winansi_get_osfhandle(fhin); + si.StartupInfo.hStdOutput = winansi_get_osfhandle(fhout); + si.StartupInfo.hStdError = winansi_get_osfhandle(fherr); - if (xutftowcs_path(wcmd, cmd) < 0) + /* The list of handles cannot contain duplicates */ + if (si.StartupInfo.hStdInput != INVALID_HANDLE_VALUE) + stdhandles[stdhandles_count++] = si.StartupInfo.hStdInput; + if (si.StartupInfo.hStdOutput != INVALID_HANDLE_VALUE && + si.StartupInfo.hStdOutput != si.StartupInfo.hStdInput) + stdhandles[stdhandles_count++] = si.StartupInfo.hStdOutput; + if (si.StartupInfo.hStdError != INVALID_HANDLE_VALUE && + si.StartupInfo.hStdError != si.StartupInfo.hStdInput && + si.StartupInfo.hStdError != si.StartupInfo.hStdOutput) + stdhandles[stdhandles_count++] = si.StartupInfo.hStdError; + if (stdhandles_count) + si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + + /* executables and the current directory don't support long paths */ + if (*argv && !strcmp(cmd, *argv)) + wcmd[0] = L'\0'; + else if (xutftowcs_path(wcmd, cmd) < 0) return -1; if (dir && xutftowcs_path(wdir, dir) < 0) return -1; @@ -1284,16 +1405,97 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen wenvblk = make_environment_block(deltaenv); memset(&pi, 0, sizeof(pi)); - ret = CreateProcessW(wcmd, wargs, NULL, NULL, TRUE, flags, - wenvblk, dir ? wdir : NULL, &si, &pi); + if (restrict_handle_inheritance && stdhandles_count && + (InitializeProcThreadAttributeList(NULL, 1, 0, &size) || + GetLastError() == ERROR_INSUFFICIENT_BUFFER) && + (attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST) + (HeapAlloc(GetProcessHeap(), 0, size))) && + InitializeProcThreadAttributeList(attr_list, 1, 0, &size) && + UpdateProcThreadAttribute(attr_list, 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + stdhandles, + stdhandles_count * sizeof(HANDLE), + NULL, NULL)) { + si.lpAttributeList = attr_list; + flags |= EXTENDED_STARTUPINFO_PRESENT; + } + + ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, + stdhandles_count ? TRUE : FALSE, + flags, wenvblk, dir ? wdir : NULL, + &si.StartupInfo, &pi); + + /* + * On Windows 2008 R2, it seems that specifying certain types of handles + * (such as FILE_TYPE_CHAR or FILE_TYPE_PIPE) will always produce an + * error. Rather than playing finicky and fragile games, let's just try + * to detect this situation and simply try again without restricting any + * handle inheritance. This is still better than failing to create + * processes. + */ + if (!ret && restrict_handle_inheritance && stdhandles_count) { + DWORD err = GetLastError(); + struct strbuf buf = STRBUF_INIT; + + if (err != ERROR_NO_SYSTEM_RESOURCES && + /* + * On Windows 7 and earlier, handles on pipes and character + * devices are inherited automatically, and cannot be + * specified in the thread handle list. Rather than trying + * to catch each and every corner case (and running the + * chance of *still* forgetting a few), let's just fall + * back to creating the process without trying to limit the + * handle inheritance. + */ + !(err == ERROR_INVALID_PARAMETER && + GetVersion() >> 16 < 9200) && + !getenv("SUPPRESS_HANDLE_INHERITANCE_WARNING")) { + DWORD fl = 0; + int i; + + setenv("SUPPRESS_HANDLE_INHERITANCE_WARNING", "1", 1); + + for (i = 0; i < stdhandles_count; i++) { + HANDLE h = stdhandles[i]; + strbuf_addf(&buf, "handle #%d: %p (type %lx, " + "handle info (%d) %lx\n", i, h, + GetFileType(h), + GetHandleInformation(h, &fl), + fl); + } + strbuf_addstr(&buf, "\nThis is a bug; please report it " + "at\nhttps://github.com/git-for-windows/" + "git/issues/new\n\n" + "To suppress this warning, please set " + "the environment variable\n\n" + "\tSUPPRESS_HANDLE_INHERITANCE_WARNING=1" + "\n"); + } + restrict_handle_inheritance = 0; + flags &= ~EXTENDED_STARTUPINFO_PRESENT; + ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, + TRUE, flags, wenvblk, dir ? wdir : NULL, + &si.StartupInfo, &pi); + if (ret && buf.len) { + errno = err_win_to_posix(GetLastError()); + warning("failed to restrict file handles (%ld)\n\n%s", + err, buf.buf); + } + strbuf_release(&buf); + } else if (!ret) + errno = err_win_to_posix(GetLastError()); + + if (si.lpAttributeList) + DeleteProcThreadAttributeList(si.lpAttributeList); + if (attr_list) + HeapFree(GetProcessHeap(), 0, attr_list); free(wenvblk); free(wargs); - if (!ret) { - errno = ENOENT; + if (!ret) return -1; - } + CloseHandle(pi.hThread); /* @@ -1673,7 +1875,8 @@ static void ensure_socket_initialization(void) WSAGetLastError()); for (name = libraries; *name; name++) { - ipv6_dll = LoadLibrary(*name); + ipv6_dll = LoadLibraryExA(*name, NULL, + LOAD_LIBRARY_SEARCH_SYSTEM32); if (!ipv6_dll) continue; @@ -1824,8 +2027,9 @@ int mingw_rename(const char *pold, const char *pnew) { DWORD attrs, gle; int tries = 0; - wchar_t wpold[MAX_PATH], wpnew[MAX_PATH]; - if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 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; /* @@ -2108,24 +2312,12 @@ int mingw_raise(int sig) int link(const char *oldpath, const char *newpath) { - typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); - static T create_hard_link = NULL; - wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH]; - if (xutftowcs_path(woldpath, oldpath) < 0 || - xutftowcs_path(wnewpath, newpath) < 0) + wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH]; + if (xutftowcs_long_path(woldpath, oldpath) < 0 || + xutftowcs_long_path(wnewpath, newpath) < 0) return -1; - if (!create_hard_link) { - create_hard_link = (T) GetProcAddress( - GetModuleHandle("kernel32.dll"), "CreateHardLinkW"); - if (!create_hard_link) - create_hard_link = (T)-1; - } - if (create_hard_link == (T)-1) { - errno = ENOSYS; - return -1; - } - if (!create_hard_link(wnewpath, woldpath, NULL)) { + if (!CreateHardLinkW(wnewpath, woldpath, NULL)) { errno = err_win_to_posix(GetLastError()); return -1; } @@ -2325,6 +2517,68 @@ static void setup_windows_environment(void) setenv("TERM", "cygwin", 1); } +int handle_long_path(wchar_t *path, int len, int max_path, int expand) +{ + int result; + wchar_t buf[MAX_LONG_PATH]; + + /* + * we don't need special handling if path is relative to the current + * directory, and current directory + path don't exceed the desired + * max_path limit. This should cover > 99 % of cases with minimal + * performance impact (git almost always uses relative paths). + */ + if ((len < 2 || (!is_dir_sep(path[0]) && path[1] != ':')) && + (current_directory_len + len < max_path)) + return len; + + /* + * handle everything else: + * - absolute paths: "C:\dir\file" + * - absolute UNC paths: "\\server\share\dir\file" + * - absolute paths on current drive: "\dir\file" + * - relative paths on other drive: "X:file" + * - prefixed paths: "\\?\...", "\\.\..." + */ + + /* convert to absolute path using GetFullPathNameW */ + result = GetFullPathNameW(path, MAX_LONG_PATH, buf, NULL); + if (!result) { + errno = err_win_to_posix(GetLastError()); + return -1; + } + + /* + * return absolute path if it fits within max_path (even if + * "cwd + path" doesn't due to '..' components) + */ + if (result < max_path) { + wcscpy(path, buf); + return result; + } + + /* error out if we shouldn't expand the path or buf is too small */ + if (!expand || result >= MAX_LONG_PATH - 6) { + errno = ENAMETOOLONG; + return -1; + } + + /* prefix full path with "\\?\" or "\\?\UNC\" */ + if (buf[0] == '\\') { + /* ...unless already prefixed */ + if (buf[1] == '\\' && (buf[2] == '?' || buf[2] == '.')) + return len; + + wcscpy(path, L"\\\\?\\UNC\\"); + wcscpy(path + 8, buf + 2); + return result + 6; + } else { + wcscpy(path, L"\\\\?\\"); + wcscpy(path + 4, buf); + return result + 4; + } +} + /* * Disable MSVCRT command line wildcard expansion (__getmainargs called from * mingw startup code, see init.c in mingw runtime). @@ -2462,6 +2716,8 @@ void mingw_startup(void) /* fix Windows specific environment settings */ setup_windows_environment(); + unset_environment_variables = xstrdup("PERL5LIB"); + /* initialize critical section for waitpid pinfo_t list */ InitializeCriticalSection(&pinfo_cs); @@ -2473,6 +2729,9 @@ void mingw_startup(void) /* initialize Unicode console */ winansi_init(); + + /* init length of current directory for handle_long_path */ + current_directory_len = GetCurrentDirectoryW(0, NULL); } int uname(struct utsname *buf) diff --git a/compat/mingw.h b/compat/mingw.h index 45667999ce..92e1290830 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -11,6 +11,12 @@ typedef _sigset_t sigset_t; #undef _POSIX_THREAD_SAFE_FUNCTIONS #endif +extern int core_fscache; +extern int core_long_paths; + +extern int mingw_core_config(const char *var, const char *value, void *cb); +#define platform_core_config mingw_core_config + /* * things that are not available in header files */ @@ -326,6 +332,22 @@ static inline int getrlimit(int resource, struct rlimit *rlp) return 0; } +/* + * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC. + * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch. + */ +static inline long long filetime_to_hnsec(const FILETIME *ft) +{ + long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime; + /* Windows to Unix Epoch conversion */ + return winTime - 116444736000000000LL; +} + +static inline time_t filetime_to_time_t(const FILETIME *ft) +{ + return (time_t)(filetime_to_hnsec(ft) / 10000000); +} + /* * Use mingw specific stat()/lstat()/fstat() implementations on Windows. */ @@ -349,7 +371,7 @@ int mingw_fstat(int fd, struct stat *buf); #ifdef lstat #undef lstat #endif -#define lstat mingw_lstat +extern int (*lstat)(const char *file_name, struct stat *buf); #ifndef _stati64 # define _stati64(x,y) mingw_stat(x,y) @@ -390,6 +412,9 @@ int mingw_raise(int sig); int winansi_isatty(int fd); #define isatty winansi_isatty +int winansi_dup2(int oldfd, int newfd); +#define dup2 winansi_dup2 + void winansi_init(void); HANDLE winansi_get_osfhandle(int fd); @@ -435,6 +460,42 @@ extern const char *program_data_config(void); #include #endif +/** + * Max length of long paths (exceeding MAX_PATH). The actual maximum supported + * by NTFS is 32,767 (* sizeof(wchar_t)), but we choose an arbitrary smaller + * value to limit required stack memory. + */ +#define MAX_LONG_PATH 4096 + +/** + * Handles paths that would exceed the MAX_PATH limit of Windows Unicode APIs. + * + * With expand == false, the function checks for over-long paths and fails + * with ENAMETOOLONG. The path parameter is not modified, except if cwd + path + * exceeds max_path, but the resulting absolute path doesn't (e.g. due to + * eliminating '..' components). The path parameter must point to a buffer + * of max_path wide characters. + * + * With expand == true, an over-long path is automatically converted in place + * to an absolute path prefixed with '\\?\', and the new length is returned. + * The path parameter must point to a buffer of MAX_LONG_PATH wide characters. + * + * Parameters: + * path: path to check and / or convert + * len: size of path on input (number of wide chars without \0) + * max_path: max short path length to check (usually MAX_PATH = 260, but just + * 248 for CreateDirectoryW) + * expand: false to only check the length, true to expand the path to a + * '\\?\'-prefixed absolute path + * + * Return: + * length of the resulting path, or -1 on failure + * + * Errors: + * ENAMETOOLONG if path is too long + */ +int handle_long_path(wchar_t *path, int len, int max_path, int expand); + /** * Converts UTF-8 encoded string to UTF-16LE. * @@ -492,17 +553,45 @@ static inline int xutftowcs(wchar_t *wcs, const char *utf, size_t wcslen) return xutftowcsn(wcs, utf, wcslen, -1); } +/** + * Simplified file system specific wrapper of xutftowcsn and handle_long_path. + * Converts ERANGE to ENAMETOOLONG. If expand is true, wcs must be at least + * MAX_LONG_PATH wide chars (see handle_long_path). + */ +static inline int xutftowcs_path_ex(wchar_t *wcs, const char *utf, + size_t wcslen, int utflen, int max_path, int expand) +{ + int result = xutftowcsn(wcs, utf, wcslen, utflen); + if (result < 0 && errno == ERANGE) + errno = ENAMETOOLONG; + if (result >= 0) + result = handle_long_path(wcs, result, max_path, expand); + return result; +} + /** * Simplified file system specific variant of xutftowcsn, assumes output * buffer size is MAX_PATH wide chars and input string is \0-terminated, - * fails with ENAMETOOLONG if input string is too long. + * fails with ENAMETOOLONG if input string is too long. Typically used for + * Windows APIs that don't support long paths, e.g. SetCurrentDirectory, + * LoadLibrary, CreateProcess... */ static inline int xutftowcs_path(wchar_t *wcs, const char *utf) { - int result = xutftowcsn(wcs, utf, MAX_PATH, -1); - if (result < 0 && errno == ERANGE) - errno = ENAMETOOLONG; - return result; + return xutftowcs_path_ex(wcs, utf, MAX_PATH, -1, MAX_PATH, 0); +} + +/** + * Simplified file system specific variant of xutftowcsn for Windows APIs + * that support long paths via '\\?\'-prefix, assumes output buffer size is + * MAX_LONG_PATH wide chars, fails with ENAMETOOLONG if input string is too + * long. The 'core.longpaths' git-config option controls whether the path + * is only checked or expanded to a long path. + */ +static inline int xutftowcs_long_path(wchar_t *wcs, const char *utf) +{ + return xutftowcs_path_ex(wcs, utf, MAX_LONG_PATH, -1, MAX_PATH, + core_long_paths); } /** diff --git a/compat/poll/poll.c b/compat/poll/poll.c index 7ed3fbbea1..598da7342a 100644 --- a/compat/poll/poll.c +++ b/compat/poll/poll.c @@ -29,9 +29,6 @@ #include -/* Specification. */ -#include - #include #include #include @@ -55,6 +52,9 @@ # include #endif +/* Specification. */ +#include "poll.h" + #ifdef HAVE_SYS_IOCTL_H # include #endif @@ -266,6 +266,20 @@ win32_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents) return happened; } +#include +#include "compat/win32/lazyload.h" + +static ULONGLONG CompatGetTickCount64(void) +{ + DECLARE_PROC_ADDR(kernel32.dll, ULONGLONG, GetTickCount64, void); + + if (!INIT_PROC_ADDR(GetTickCount64)) + return (ULONGLONG)GetTickCount(); + + return GetTickCount64(); +} +#define GetTickCount64 CompatGetTickCount64 + #else /* !MinGW */ /* Convert select(2) returned fd_sets into poll(2) revents values. */ @@ -449,7 +463,8 @@ poll (struct pollfd *pfd, nfds_t nfd, int timeout) static HANDLE hEvent; WSANETWORKEVENTS ev; HANDLE h, handle_array[FD_SETSIZE + 2]; - DWORD ret, wait_timeout, nhandles, start = 0, elapsed, orig_timeout = 0; + DWORD ret, wait_timeout, nhandles, elapsed, orig_timeout = 0; + ULONGLONG start = 0; fd_set rfds, wfds, xfds; BOOL poll_again; MSG msg; @@ -465,7 +480,7 @@ poll (struct pollfd *pfd, nfds_t nfd, int timeout) if (timeout != INFTIM) { orig_timeout = timeout; - start = GetTickCount(); + start = GetTickCount64(); } if (!hEvent) @@ -614,7 +629,7 @@ restart: if (!rc && orig_timeout && timeout != INFTIM) { - elapsed = GetTickCount() - start; + elapsed = (DWORD)(GetTickCount64() - start); timeout = elapsed >= orig_timeout ? 0 : orig_timeout - elapsed; } diff --git a/compat/poll/poll.h b/compat/poll/poll.h index cd1995292a..1e1597360f 100644 --- a/compat/poll/poll.h +++ b/compat/poll/poll.h @@ -21,6 +21,21 @@ #ifndef _GL_POLL_H #define _GL_POLL_H +#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600 +/* Vista has its own, socket-only poll() */ +#undef POLLIN +#undef POLLPRI +#undef POLLOUT +#undef POLLERR +#undef POLLHUP +#undef POLLNVAL +#undef POLLRDNORM +#undef POLLRDBAND +#undef POLLWRNORM +#undef POLLWRBAND +#define pollfd compat_pollfd +#endif + /* fake a poll(2) environment */ #define POLLIN 0x0001 /* any readable data available */ #define POLLPRI 0x0002 /* OOB/Urgent readable data */ diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c index 52420ec7d4..b3bd8d7af7 100644 --- a/compat/win32/dirent.c +++ b/compat/win32/dirent.c @@ -1,15 +1,19 @@ #include "../../git-compat-util.h" -struct DIR { +typedef struct dirent_DIR { + struct DIR base_dir; /* extend base struct DIR */ struct dirent dd_dir; /* includes d_type */ HANDLE dd_handle; /* FindFirstFile handle */ int dd_stat; /* 0-based index */ -}; + char dd_name[MAX_PATH * 3]; /* file name (* 3 for UTF-8 conversion) */ +} dirent_DIR; + +DIR *(*opendir)(const char *dirname) = dirent_opendir; static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata) { - /* convert UTF-16 name to UTF-8 */ - xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name)); + /* convert UTF-16 name to UTF-8 (d_name points to dirent_DIR.dd_name) */ + xwcstoutf(ent->d_name, fdata->cFileName, MAX_PATH * 3); /* Set file type, based on WIN32_FIND_DATA */ if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) @@ -18,41 +22,7 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata) ent->d_type = DT_REG; } -DIR *opendir(const char *name) -{ - wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */ - WIN32_FIND_DATAW fdata; - HANDLE h; - int len; - DIR *dir; - - /* convert name to UTF-16 and check length < MAX_PATH */ - if ((len = xutftowcs_path(pattern, name)) < 0) - return NULL; - - /* append optional '/' and wildcard '*' */ - if (len && !is_dir_sep(pattern[len - 1])) - pattern[len++] = '/'; - pattern[len++] = '*'; - pattern[len] = 0; - - /* open find handle */ - h = FindFirstFileW(pattern, &fdata); - if (h == INVALID_HANDLE_VALUE) { - DWORD err = GetLastError(); - errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err); - return NULL; - } - - /* initialize DIR structure and copy first dir entry */ - dir = xmalloc(sizeof(DIR)); - dir->dd_handle = h; - dir->dd_stat = 0; - finddata2dirent(&dir->dd_dir, &fdata); - return dir; -} - -struct dirent *readdir(DIR *dir) +static struct dirent *dirent_readdir(dirent_DIR *dir) { if (!dir) { errno = EBADF; /* No set_errno for mingw */ @@ -79,7 +49,7 @@ struct dirent *readdir(DIR *dir) return &dir->dd_dir; } -int closedir(DIR *dir) +static int dirent_closedir(dirent_DIR *dir) { if (!dir) { errno = EBADF; @@ -90,3 +60,44 @@ int closedir(DIR *dir) free(dir); return 0; } + +DIR *dirent_opendir(const char *name) +{ + wchar_t pattern[MAX_LONG_PATH + 2]; /* + 2 for "\*" */ + WIN32_FIND_DATAW fdata; + HANDLE h; + int len; + dirent_DIR *dir; + + /* convert name to UTF-16 and check length */ + if ((len = xutftowcs_path_ex(pattern, name, MAX_LONG_PATH, -1, + MAX_PATH - 2, core_long_paths)) < 0) + return NULL; + + /* + * append optional '\' and wildcard '*'. Note: we need to use '\' as + * Windows doesn't translate '/' to '\' for "\\?\"-prefixed paths. + */ + if (len && !is_dir_sep(pattern[len - 1])) + pattern[len++] = '\\'; + pattern[len++] = '*'; + pattern[len] = 0; + + /* open find handle */ + h = FindFirstFileW(pattern, &fdata); + if (h == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err); + return NULL; + } + + /* initialize DIR structure and copy first dir entry */ + dir = xmalloc(sizeof(dirent_DIR)); + dir->base_dir.preaddir = (struct dirent *(*)(DIR *dir)) dirent_readdir; + dir->base_dir.pclosedir = (int (*)(DIR *dir)) dirent_closedir; + dir->dd_dir.d_name = dir->dd_name; + dir->dd_handle = h; + dir->dd_stat = 0; + finddata2dirent(&dir->dd_dir, &fdata); + return (DIR*) dir; +} diff --git a/compat/win32/dirent.h b/compat/win32/dirent.h index 058207e4bf..6b3ddee51b 100644 --- a/compat/win32/dirent.h +++ b/compat/win32/dirent.h @@ -1,20 +1,32 @@ #ifndef DIRENT_H #define DIRENT_H -typedef struct DIR DIR; - #define DT_UNKNOWN 0 #define DT_DIR 1 #define DT_REG 2 #define DT_LNK 3 struct dirent { - unsigned char d_type; /* file type to prevent lstat after readdir */ - char d_name[MAX_PATH * 3]; /* file name (* 3 for UTF-8 conversion) */ + unsigned char d_type; /* file type to prevent lstat after readdir */ + char *d_name; /* file name */ }; -DIR *opendir(const char *dirname); -struct dirent *readdir(DIR *dir); -int closedir(DIR *dir); +/* + * Base DIR structure, contains pointers to readdir/closedir implementations so + * that opendir may choose a concrete implementation on a call-by-call basis. + */ +typedef struct DIR { + struct dirent *(*preaddir)(struct DIR *dir); + int (*pclosedir)(struct DIR *dir); +} DIR; + +/* default dirent implementation */ +extern DIR *dirent_opendir(const char *dirname); + +/* current dirent implementation */ +extern DIR *(*opendir)(const char *dirname); + +#define readdir(dir) (dir->preaddir(dir)) +#define closedir(dir) (dir->pclosedir(dir)) #endif /* DIRENT_H */ diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c new file mode 100644 index 0000000000..162fbed09b --- /dev/null +++ b/compat/win32/fscache.c @@ -0,0 +1,492 @@ +#include "../../cache.h" +#include "../../hashmap.h" +#include "../win32.h" +#include "fscache.h" + +static int initialized; +static volatile long enabled; +static struct hashmap map; +static CRITICAL_SECTION mutex; + +/* + * An entry in the file system cache. Used for both entire directory listings + * and file entries. + */ +struct fsentry { + struct hashmap_entry ent; + mode_t st_mode; + /* Length of name. */ + unsigned short len; + /* + * Name of the entry. For directory listings: relative path of the + * directory, without trailing '/' (empty for cwd()). For file entries: + * name of the file. Typically points to the end of the structure if + * the fsentry is allocated on the heap (see fsentry_alloc), or to a + * local variable if on the stack (see fsentry_init). + */ + const char *name; + /* Pointer to the directory listing, or NULL for the listing itself. */ + struct fsentry *list; + /* Pointer to the next file entry of the list. */ + struct fsentry *next; + + union { + /* Reference count of the directory listing. */ + volatile long refcnt; + /* Handle to wait on the loading thread. */ + HANDLE hwait; + struct { + /* More stat members (only used for file entries). */ + off64_t st_size; + time_t st_atime; + time_t st_mtime; + time_t st_ctime; + }; + }; +}; + +/* + * Compares the paths of two fsentry structures for equality. + */ +static int fsentry_cmp(void *unused_cmp_data, + const struct fsentry *fse1, const struct fsentry *fse2, + void *unused_keydata) +{ + int res; + if (fse1 == fse2) + return 0; + + /* compare the list parts first */ + if (fse1->list != fse2->list && + (res = fsentry_cmp(NULL, fse1->list ? fse1->list : fse1, + fse2->list ? fse2->list : fse2, NULL))) + return res; + + /* if list parts are equal, compare len and name */ + if (fse1->len != fse2->len) + return fse1->len - fse2->len; + return strnicmp(fse1->name, fse2->name, fse1->len); +} + +/* + * Calculates the hash code of an fsentry structure's path. + */ +static unsigned int fsentry_hash(const struct fsentry *fse) +{ + unsigned int hash = fse->list ? fse->list->ent.hash : 0; + return hash ^ memihash(fse->name, fse->len); +} + +/* + * Initialize an fsentry structure for use by fsentry_hash and fsentry_cmp. + */ +static void fsentry_init(struct fsentry *fse, struct fsentry *list, + const char *name, size_t len) +{ + fse->list = list; + fse->name = name; + fse->len = len; + hashmap_entry_init(fse, fsentry_hash(fse)); +} + +/* + * Allocate an fsentry structure on the heap. + */ +static struct fsentry *fsentry_alloc(struct fsentry *list, const char *name, + size_t len) +{ + /* overallocate fsentry and copy the name to the end */ + struct fsentry *fse = xmalloc(sizeof(struct fsentry) + len + 1); + char *nm = ((char*) fse) + sizeof(struct fsentry); + memcpy(nm, name, len); + nm[len] = 0; + /* init the rest of the structure */ + fsentry_init(fse, list, nm, len); + fse->next = NULL; + fse->refcnt = 1; + return fse; +} + +/* + * Add a reference to an fsentry. + */ +inline static void fsentry_addref(struct fsentry *fse) +{ + if (fse->list) + fse = fse->list; + + InterlockedIncrement(&(fse->refcnt)); +} + +/* + * Release the reference to an fsentry, frees the memory if its the last ref. + */ +static void fsentry_release(struct fsentry *fse) +{ + if (fse->list) + fse = fse->list; + + if (InterlockedDecrement(&(fse->refcnt))) + return; + + while (fse) { + struct fsentry *next = fse->next; + free(fse); + fse = next; + } +} + +/* + * Allocate and initialize an fsentry from a WIN32_FIND_DATA structure. + */ +static struct fsentry *fseentry_create_entry(struct fsentry *list, + const WIN32_FIND_DATAW *fdata) +{ + char buf[MAX_PATH * 3]; + int len; + struct fsentry *fse; + len = xwcstoutf(buf, fdata->cFileName, ARRAY_SIZE(buf)); + + 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_atime = filetime_to_time_t(&(fdata->ftLastAccessTime)); + fse->st_mtime = filetime_to_time_t(&(fdata->ftLastWriteTime)); + fse->st_ctime = filetime_to_time_t(&(fdata->ftCreationTime)); + + return fse; +} + +/* + * Create an fsentry-based directory listing (similar to opendir / readdir). + * Dir should not contain trailing '/'. Use an empty string for the current + * directory (not "."!). + */ +static struct fsentry *fsentry_create_list(const struct fsentry *dir) +{ + wchar_t pattern[MAX_LONG_PATH + 2]; /* + 2 for "\*" */ + WIN32_FIND_DATAW fdata; + HANDLE h; + int wlen; + struct fsentry *list, **phead; + DWORD err; + + /* convert name to UTF-16 and check length */ + if ((wlen = xutftowcs_path_ex(pattern, dir->name, MAX_LONG_PATH, + dir->len, MAX_PATH - 2, core_long_paths)) < 0) + return NULL; + + /* + * append optional '\' and wildcard '*'. Note: we need to use '\' as + * Windows doesn't translate '/' to '\' for "\\?\"-prefixed paths. + */ + if (wlen) + pattern[wlen++] = '\\'; + pattern[wlen++] = '*'; + pattern[wlen] = 0; + + /* open find handle */ + h = FindFirstFileW(pattern, &fdata); + if (h == INVALID_HANDLE_VALUE) { + err = GetLastError(); + errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err); + return NULL; + } + + /* allocate object to hold directory listing */ + list = fsentry_alloc(NULL, dir->name, dir->len); + + /* walk directory and build linked list of fsentry structures */ + phead = &list->next; + do { + *phead = fseentry_create_entry(list, &fdata); + phead = &(*phead)->next; + } while (FindNextFileW(h, &fdata)); + + /* remember result of last FindNextFile, then close find handle */ + err = GetLastError(); + FindClose(h); + + /* return the list if we've got all the files */ + if (err == ERROR_NO_MORE_FILES) + return list; + + /* otherwise free the list and return error */ + fsentry_release(list); + errno = err_win_to_posix(err); + return NULL; +} + +/* + * Adds a directory listing to the cache. + */ +static void fscache_add(struct fsentry *fse) +{ + if (fse->list) + fse = fse->list; + + for (; fse; fse = fse->next) + hashmap_add(&map, fse); +} + +/* + * Clears the cache. + */ +static void fscache_clear(void) +{ + hashmap_free(&map, 1); + hashmap_init(&map, (hashmap_cmp_fn)fsentry_cmp, NULL, 0); +} + +/* + * Checks if the cache is enabled for the given path. + */ +static inline int fscache_enabled(const char *path) +{ + return enabled > 0 && !is_absolute_path(path); +} + +/* + * Looks up a cache entry, waits if its being loaded by another thread. + * The mutex must be owned by the calling thread. + */ +static struct fsentry *fscache_get_wait(struct fsentry *key) +{ + struct fsentry *fse = hashmap_get(&map, key, NULL); + + /* return if its a 'real' entry (future entries have refcnt == 0) */ + if (!fse || fse->list || fse->refcnt) + return fse; + + /* create an event and link our key to the future entry */ + key->hwait = CreateEvent(NULL, TRUE, FALSE, NULL); + key->next = fse->next; + fse->next = key; + + /* wait for the loading thread to signal us */ + LeaveCriticalSection(&mutex); + WaitForSingleObject(key->hwait, INFINITE); + CloseHandle(key->hwait); + EnterCriticalSection(&mutex); + + /* repeat cache lookup */ + return hashmap_get(&map, key, NULL); +} + +/* + * Looks up or creates a cache entry for the specified key. + */ +static struct fsentry *fscache_get(struct fsentry *key) +{ + struct fsentry *fse, *future, *waiter; + + EnterCriticalSection(&mutex); + /* check if entry is in cache */ + fse = fscache_get_wait(key); + if (fse) { + fsentry_addref(fse); + LeaveCriticalSection(&mutex); + return fse; + } + /* if looking for a file, check if directory listing is in cache */ + if (!fse && key->list) { + fse = fscache_get_wait(key->list); + if (fse) { + LeaveCriticalSection(&mutex); + /* dir entry without file entry -> file doesn't exist */ + errno = ENOENT; + return NULL; + } + } + + /* add future entry to indicate that we're loading it */ + future = key->list ? key->list : key; + future->next = NULL; + future->refcnt = 0; + hashmap_add(&map, future); + + /* create the directory listing (outside mutex!) */ + LeaveCriticalSection(&mutex); + fse = fsentry_create_list(future); + EnterCriticalSection(&mutex); + + /* remove future entry and signal waiting threads */ + hashmap_remove(&map, future, NULL); + waiter = future->next; + while (waiter) { + HANDLE h = waiter->hwait; + waiter = waiter->next; + SetEvent(h); + } + + /* leave on error (errno set by fsentry_create_list) */ + if (!fse) { + LeaveCriticalSection(&mutex); + return NULL; + } + + /* add directory listing to the cache */ + fscache_add(fse); + + /* lookup file entry if requested (fse already points to directory) */ + if (key->list) + fse = hashmap_get(&map, key, NULL); + + /* return entry or ENOENT */ + if (fse) + fsentry_addref(fse); + else + errno = ENOENT; + + LeaveCriticalSection(&mutex); + return fse; +} + +/* + * Enables or disables the cache. Note that the cache is read-only, changes to + * the working directory are NOT reflected in the cache while enabled. + */ +int fscache_enable(int enable) +{ + int result; + + if (!initialized) { + /* allow the cache to be disabled entirely */ + if (!core_fscache) + return 0; + + InitializeCriticalSection(&mutex); + hashmap_init(&map, (hashmap_cmp_fn) fsentry_cmp, NULL, 0); + initialized = 1; + } + + result = enable ? InterlockedIncrement(&enabled) + : InterlockedDecrement(&enabled); + + if (enable && result == 1) { + /* redirect opendir and lstat to the fscache implementations */ + opendir = fscache_opendir; + lstat = fscache_lstat; + } else if (!enable && !result) { + /* reset opendir and lstat to the original implementations */ + opendir = dirent_opendir; + lstat = mingw_lstat; + EnterCriticalSection(&mutex); + fscache_clear(); + LeaveCriticalSection(&mutex); + } + return result; +} + +/* + * Lstat replacement, uses the cache if enabled, otherwise redirects to + * mingw_lstat. + */ +int fscache_lstat(const char *filename, struct stat *st) +{ + int dirlen, base, len; + struct fsentry key[2], *fse; + + if (!fscache_enabled(filename)) + return mingw_lstat(filename, st); + + /* split filename into path + name */ + len = strlen(filename); + if (len && is_dir_sep(filename[len - 1])) + len--; + base = len; + while (base && !is_dir_sep(filename[base - 1])) + base--; + dirlen = base ? base - 1 : 0; + + /* lookup entry for path + name in cache */ + fsentry_init(key, NULL, filename, dirlen); + fsentry_init(key + 1, key, filename + base, len - base); + fse = fscache_get(key + 1); + if (!fse) + return -1; + + /* copy stat data */ + st->st_ino = 0; + st->st_gid = 0; + st->st_uid = 0; + st->st_dev = 0; + st->st_rdev = 0; + st->st_nlink = 1; + st->st_mode = fse->st_mode; + st->st_size = fse->st_size; + st->st_atime = fse->st_atime; + st->st_mtime = fse->st_mtime; + st->st_ctime = fse->st_ctime; + + /* don't forget to release fsentry */ + fsentry_release(fse); + return 0; +} + +typedef struct fscache_DIR { + struct DIR base_dir; /* extend base struct DIR */ + struct fsentry *pfsentry; + struct dirent dirent; +} fscache_DIR; + +/* + * Readdir replacement. + */ +static struct dirent *fscache_readdir(DIR *base_dir) +{ + fscache_DIR *dir = (fscache_DIR*) base_dir; + struct fsentry *next = dir->pfsentry->next; + if (!next) + return NULL; + dir->pfsentry = next; + dir->dirent.d_type = S_ISDIR(next->st_mode) ? DT_DIR : DT_REG; + dir->dirent.d_name = (char*) next->name; + return &(dir->dirent); +} + +/* + * Closedir replacement. + */ +static int fscache_closedir(DIR *base_dir) +{ + fscache_DIR *dir = (fscache_DIR*) base_dir; + fsentry_release(dir->pfsentry); + free(dir); + return 0; +} + +/* + * Opendir replacement, uses a directory listing from the cache if enabled, + * otherwise calls original dirent implementation. + */ +DIR *fscache_opendir(const char *dirname) +{ + struct fsentry key, *list; + fscache_DIR *dir; + int len; + + if (!fscache_enabled(dirname)) + return dirent_opendir(dirname); + + /* prepare name (strip trailing '/', replace '.') */ + len = strlen(dirname); + if ((len == 1 && dirname[0] == '.') || + (len && is_dir_sep(dirname[len - 1]))) + len--; + + /* get directory listing from cache */ + fsentry_init(&key, NULL, dirname, len); + list = fscache_get(&key); + if (!list) + return NULL; + + /* alloc and return DIR structure */ + dir = (fscache_DIR*) xmalloc(sizeof(fscache_DIR)); + dir->base_dir.preaddir = fscache_readdir; + dir->base_dir.pclosedir = fscache_closedir; + dir->pfsentry = list; + return (DIR*) dir; +} diff --git a/compat/win32/fscache.h b/compat/win32/fscache.h new file mode 100644 index 0000000000..ed518b422d --- /dev/null +++ b/compat/win32/fscache.h @@ -0,0 +1,10 @@ +#ifndef FSCACHE_H +#define FSCACHE_H + +int fscache_enable(int enable); +#define enable_fscache(x) fscache_enable(x) + +DIR *fscache_opendir(const char *dir); +int fscache_lstat(const char *file_name, struct stat *buf); + +#endif diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c index e18f5c6e2e..2e7eead42c 100644 --- a/compat/win32/pthread.c +++ b/compat/win32/pthread.c @@ -56,141 +56,3 @@ pthread_t pthread_self(void) t.tid = GetCurrentThreadId(); return t; } - -int pthread_cond_init(pthread_cond_t *cond, const void *unused) -{ - cond->waiters = 0; - cond->was_broadcast = 0; - InitializeCriticalSection(&cond->waiters_lock); - - cond->sema = CreateSemaphore(NULL, 0, LONG_MAX, NULL); - if (!cond->sema) - die("CreateSemaphore() failed"); - - cond->continue_broadcast = CreateEvent(NULL, /* security */ - FALSE, /* auto-reset */ - FALSE, /* not signaled */ - NULL); /* name */ - if (!cond->continue_broadcast) - die("CreateEvent() failed"); - - return 0; -} - -int pthread_cond_destroy(pthread_cond_t *cond) -{ - CloseHandle(cond->sema); - CloseHandle(cond->continue_broadcast); - DeleteCriticalSection(&cond->waiters_lock); - return 0; -} - -int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex) -{ - int last_waiter; - - EnterCriticalSection(&cond->waiters_lock); - cond->waiters++; - LeaveCriticalSection(&cond->waiters_lock); - - /* - * Unlock external mutex and wait for signal. - * NOTE: we've held mutex locked long enough to increment - * waiters count above, so there's no problem with - * leaving mutex unlocked before we wait on semaphore. - */ - LeaveCriticalSection(mutex); - - /* let's wait - ignore return value */ - WaitForSingleObject(cond->sema, INFINITE); - - /* - * Decrease waiters count. If we are the last waiter, then we must - * notify the broadcasting thread that it can continue. - * But if we continued due to cond_signal, we do not have to do that - * because the signaling thread knows that only one waiter continued. - */ - EnterCriticalSection(&cond->waiters_lock); - cond->waiters--; - last_waiter = cond->was_broadcast && cond->waiters == 0; - LeaveCriticalSection(&cond->waiters_lock); - - if (last_waiter) { - /* - * cond_broadcast was issued while mutex was held. This means - * that all other waiters have continued, but are contending - * for the mutex at the end of this function because the - * broadcasting thread did not leave cond_broadcast, yet. - * (This is so that it can be sure that each waiter has - * consumed exactly one slice of the semaphor.) - * The last waiter must tell the broadcasting thread that it - * can go on. - */ - SetEvent(cond->continue_broadcast); - /* - * Now we go on to contend with all other waiters for - * the mutex. Auf in den Kampf! - */ - } - /* lock external mutex again */ - EnterCriticalSection(mutex); - - return 0; -} - -/* - * IMPORTANT: This implementation requires that pthread_cond_signal - * is called while the mutex is held that is used in the corresponding - * pthread_cond_wait calls! - */ -int pthread_cond_signal(pthread_cond_t *cond) -{ - int have_waiters; - - EnterCriticalSection(&cond->waiters_lock); - have_waiters = cond->waiters > 0; - LeaveCriticalSection(&cond->waiters_lock); - - /* - * Signal only when there are waiters - */ - if (have_waiters) - return ReleaseSemaphore(cond->sema, 1, NULL) ? - 0 : err_win_to_posix(GetLastError()); - else - return 0; -} - -/* - * DOUBLY IMPORTANT: This implementation requires that pthread_cond_broadcast - * is called while the mutex is held that is used in the corresponding - * pthread_cond_wait calls! - */ -int pthread_cond_broadcast(pthread_cond_t *cond) -{ - EnterCriticalSection(&cond->waiters_lock); - - if ((cond->was_broadcast = cond->waiters > 0)) { - /* wake up all waiters */ - ReleaseSemaphore(cond->sema, cond->waiters, NULL); - LeaveCriticalSection(&cond->waiters_lock); - /* - * At this point all waiters continue. Each one takes its - * slice of the semaphor. Now it's our turn to wait: Since - * the external mutex is held, no thread can leave cond_wait, - * yet. For this reason, we can be sure that no thread gets - * a chance to eat *more* than one slice. OTOH, it means - * that the last waiter must send us a wake-up. - */ - WaitForSingleObject(cond->continue_broadcast, INFINITE); - /* - * Since the external mutex is held, no thread can enter - * cond_wait, and, hence, it is safe to reset this flag - * without cond->waiters_lock held. - */ - cond->was_broadcast = 0; - } else { - LeaveCriticalSection(&cond->waiters_lock); - } - return 0; -} diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h index 1c164088fb..11383eb5f4 100644 --- a/compat/win32/pthread.h +++ b/compat/win32/pthread.h @@ -32,27 +32,24 @@ typedef int pthread_mutexattr_t; #define pthread_mutexattr_settype(a, t) 0 #define PTHREAD_MUTEX_RECURSIVE 0 -/* - * Implement simple condition variable for Windows threads, based on ACE - * implementation. - * - * See original implementation: http://bit.ly/1vkDjo - * ACE homepage: http://www.cse.wustl.edu/~schmidt/ACE.html - * See also: http://www.cse.wustl.edu/~schmidt/win32-cv-1.html - */ -typedef struct { - LONG waiters; - int was_broadcast; - CRITICAL_SECTION waiters_lock; - HANDLE sema; - HANDLE continue_broadcast; -} pthread_cond_t; +#define pthread_cond_t CONDITION_VARIABLE -extern int pthread_cond_init(pthread_cond_t *cond, const void *unused); -extern int pthread_cond_destroy(pthread_cond_t *cond); -extern int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex); -extern int pthread_cond_signal(pthread_cond_t *cond); -extern int pthread_cond_broadcast(pthread_cond_t *cond); +WINBASEAPI VOID WINAPI +InitializeConditionVariable(PCONDITION_VARIABLE ConditionVariable); +WINBASEAPI VOID WINAPI +WakeConditionVariable(PCONDITION_VARIABLE ConditionVariable); +WINBASEAPI VOID WINAPI +WakeAllConditionVariable(PCONDITION_VARIABLE ConditionVariable); +WINBASEAPI BOOL WINAPI +SleepConditionVariableCS(PCONDITION_VARIABLE ConditionVariable, + PCRITICAL_SECTION CriticalSection, + DWORD dwMilliseconds); + +#define pthread_cond_init(a,b) InitializeConditionVariable((a)) +#define pthread_cond_destroy(a) do {} while (0) +#define pthread_cond_wait(a,b) return_0(SleepConditionVariableCS((a), (b), INFINITE)) +#define pthread_cond_signal WakeConditionVariable +#define pthread_cond_broadcast WakeAllConditionVariable /* * Simple thread creation implementation using pthread API diff --git a/compat/winansi.c b/compat/winansi.c index a11a0f16d2..d81547fa0f 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -474,6 +474,18 @@ static void die_lasterr(const char *fmt, ...) va_end(params); } +#undef dup2 +int winansi_dup2(int oldfd, int newfd) +{ + int ret = dup2(oldfd, newfd); + + if (!ret && newfd >= 0 && newfd <= 2) + fd_is_interactive[newfd] = oldfd < 0 || oldfd > 2 ? + 0 : fd_is_interactive[oldfd]; + + return ret; +} + static HANDLE duplicate_handle(HANDLE hnd) { HANDLE hresult, hproc = GetCurrentProcess(); @@ -639,10 +651,20 @@ void winansi_init(void) */ HANDLE winansi_get_osfhandle(int fd) { + HANDLE ret; + if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED)) return hconsole1; if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED)) return hconsole2; - return (HANDLE)_get_osfhandle(fd); + ret = (HANDLE)_get_osfhandle(fd); + + /* + * There are obviously circumstances under which _get_osfhandle() + * returns (HANDLE)-2. This is not documented anywhere, but that is so + * clearly an invalid handle value that we can just work around this + * and return the correct value for invalid handles. + */ + return ret == (HANDLE)-2 ? INVALID_HANDLE_VALUE : ret; } diff --git a/config.c b/config.c index 0336fa9989..1a0e950c3c 100644 --- a/config.c +++ b/config.c @@ -1093,7 +1093,7 @@ int git_config_color(char *dest, const char *var, const char *value) return 0; } -static int git_default_core_config(const char *var, const char *value) +static int git_default_core_config(const char *var, const char *value, void *cb) { /* This needs a better name */ if (!strcmp(var, "core.filemode")) { @@ -1344,14 +1344,6 @@ static int git_default_core_config(const char *var, const char *value) return 0; } - if (!strcmp(var, "core.hidedotfiles")) { - if (value && !strcasecmp(value, "dotgitonly")) - hide_dotfiles = HIDE_DOTFILES_DOTGITONLY; - else - hide_dotfiles = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "core.partialclonefilter")) { return git_config_string(&core_partial_clone_filter_default, var, value); @@ -1363,7 +1355,7 @@ static int git_default_core_config(const char *var, const char *value) } /* Add other config variables here and to Documentation/config.txt. */ - return 0; + return platform_core_config(var, value, cb); } static int git_default_i18n_config(const char *var, const char *value) @@ -1448,13 +1440,13 @@ static int git_default_mailmap_config(const char *var, const char *value) return 0; } -int git_default_config(const char *var, const char *value, void *dummy) +int git_default_config(const char *var, const char *value, void *cb) { if (starts_with(var, "core.")) - return git_default_core_config(var, value); + return git_default_core_config(var, value, cb); if (starts_with(var, "user.")) - return git_ident_config(var, value, dummy); + return git_ident_config(var, value, cb); if (starts_with(var, "i18n.")) return git_default_i18n_config(var, value); @@ -1676,6 +1668,8 @@ static int do_git_config_sequence(const struct config_options *opts, if (opts->commondir) repo_config = mkpathdup("%s/config", opts->commondir); + else if (opts->git_dir) + repo_config = mkpathdup("%s/config", opts->git_dir); else repo_config = NULL; diff --git a/config.mak.uname b/config.mak.uname index e47af72e01..02cb3c137f 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -381,8 +381,6 @@ ifeq ($(uname_S),Windows) NO_PYTHON = YesPlease BLK_SHA1 = YesPlease ETAGS_TARGET = ETAGS - NO_INET_PTON = YesPlease - NO_INET_NTOP = YesPlease NO_POSIX_GOODIES = UnfortunatelyYes NATIVE_CRLF = YesPlease DEFAULT_HELP_FORMAT = html @@ -393,7 +391,7 @@ ifeq ($(uname_S),Windows) BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE COMPAT_OBJS = compat/msvc.o compat/winansi.o \ compat/win32/pthread.o compat/win32/syslog.o \ - compat/win32/dirent.o + compat/win32/dirent.o compat/win32/fscache.o COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj @@ -529,15 +527,13 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_REGEX = YesPlease NO_PYTHON = YesPlease ETAGS_TARGET = ETAGS - NO_INET_PTON = YesPlease - NO_INET_NTOP = YesPlease NO_POSIX_GOODIES = UnfortunatelyYes DEFAULT_HELP_FORMAT = html COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_OBJS += compat/mingw.o compat/winansi.o \ compat/win32/pthread.o compat/win32/syslog.o \ - compat/win32/dirent.o + compat/win32/dirent.o compat/win32/fscache.o BASIC_CFLAGS += -DWIN32 -DPROTECT_NTFS_DEFAULT=1 EXTLIBS += -lws2_32 GITLIBS += git.res @@ -560,11 +556,19 @@ else ifeq ($(shell expr "$(uname_R)" : '2\.'),2) # MSys2 prefix = /usr/ + # Enable DEP + BASIC_LDFLAGS += -Wl,--nxcompat + # Enable ASLR (unless debugging) + ifneq (,$(findstring -O,$(CFLAGS))) + BASIC_LDFLAGS += -Wl,--dynamicbase + endif ifeq (MINGW32,$(MSYSTEM)) prefix = /mingw32 + BASIC_LDFLAGS += -Wl,--pic-executable,-e,_mainCRTStartup endif ifeq (MINGW64,$(MSYSTEM)) prefix = /mingw64 + BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup else COMPAT_CFLAGS += -D_USE_32BIT_TIME_T BASIC_LDFLAGS += -Wl,--large-address-aware diff --git a/connect.c b/connect.c index 94547e5056..a45e0e3dc9 100644 --- a/connect.c +++ b/connect.c @@ -918,6 +918,10 @@ static enum protocol parse_connect_url(const char *url_orig, char **ret_host, if (protocol == PROTO_LOCAL) path = end; + else if (protocol == PROTO_FILE && *host != '/' && + !has_dos_drive_prefix(host) && + offset_1st_component(host - 2) > 1) + path = host - 2; /* include the leading "//" */ else if (protocol == PROTO_FILE && has_dos_drive_prefix(end)) path = end; /* "file://$(pwd)" may be "file://C:/projects/repo" */ else diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c index 86518cd93d..5bdad41de1 100644 --- a/contrib/credential/wincred/git-credential-wincred.c +++ b/contrib/credential/wincred/git-credential-wincred.c @@ -75,7 +75,8 @@ static CredDeleteWT CredDeleteW; static void load_cred_funcs(void) { /* load DLLs */ - advapi = LoadLibrary("advapi32.dll"); + advapi = LoadLibraryExA("advapi32.dll", NULL, + LOAD_LIBRARY_SEARCH_SYSTEM32); if (!advapi) die("failed to load advapi32.dll"); diff --git a/credential.c b/credential.c index 62be651b03..e9108a9e8a 100644 --- a/credential.c +++ b/credential.c @@ -136,7 +136,9 @@ static void credential_getpass(struct credential *c) { if (!c->username) c->username = credential_ask_one("Username", c, - PROMPT_ASKPASS|PROMPT_ECHO); + (getenv("GIT_ASKPASS") ? + PROMPT_ASKPASS : 0) | + PROMPT_ECHO); if (!c->password) c->password = credential_ask_one("Password", c, PROMPT_ASKPASS); diff --git a/diffcore-rename.c b/diffcore-rename.c index d775183c2f..f55287d233 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -82,6 +82,18 @@ static struct diff_rename_src *register_rename_src(struct diff_filepair *p) first = 0; last = rename_src_nr; + + if (last > 0) { + struct diff_rename_src *src = &(rename_src[last-1]); + int cmp = strcmp(one->path, src->p->one->path); + if (!cmp) + return src; + if (cmp > 0) { + first = last; + goto append_it; + } + } + while (last > first) { int next = (last + first) >> 1; struct diff_rename_src *src = &(rename_src[next]); @@ -95,6 +107,7 @@ static struct diff_rename_src *register_rename_src(struct diff_filepair *p) first = next+1; } +append_it: /* insert to make it at "first" */ ALLOC_GROW(rename_src, rename_src_nr + 1, rename_src_alloc); rename_src_nr++; diff --git a/environment.c b/environment.c index 3f3c8746c2..30ecd4e784 100644 --- a/environment.c +++ b/environment.c @@ -71,7 +71,6 @@ int core_apply_sparse_checkout; int merge_log_config = -1; int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ unsigned long pack_size_limit_cfg; -enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY; enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; #ifndef PROTECT_HFS_DEFAULT diff --git a/git-compat-util.h b/git-compat-util.h index 03406f21a7..1acb7c9dbd 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -146,8 +146,8 @@ #define _SGI_SOURCE 1 #if defined(WIN32) && !defined(__CYGWIN__) /* Both MinGW and MSVC */ -# if defined (_MSC_VER) && !defined(_WIN32_WINNT) -# define _WIN32_WINNT 0x0502 +# if !defined(_WIN32_WINNT) +# define _WIN32_WINNT 0x0600 # endif #define WIN32_LEAN_AND_MEAN /* stops windows.h including winsock.h */ #include @@ -195,8 +195,10 @@ #if defined(__MINGW32__) /* pull in Windows compatibility stuff */ #include "compat/mingw.h" +#include "compat/win32/fscache.h" #elif defined(_MSC_VER) #include "compat/msvc.h" +#include "compat/win32/fscache.h" #else #include #include @@ -342,6 +344,14 @@ typedef uintmax_t timestamp_t; #define _PATH_DEFPATH "/usr/local/bin:/usr/bin:/bin" #endif +#ifndef platform_core_config +static inline int noop_core_config(const char *var, const char *value, void *cb) +{ + return 0; +} +#define platform_core_config noop_core_config +#endif + #ifndef has_dos_drive_prefix static inline int git_has_dos_drive_prefix(const char *path) { @@ -1225,6 +1235,21 @@ static inline int is_missing_file_error(int errno_) return (errno_ == ENOENT || errno_ == ENOTDIR); } +/* + * Enable/disable a read-only cache for file system data on platforms that + * support it. + * + * Implementing a live-cache is complicated and requires special platform + * support (inotify, ReadDirectoryChangesW...). enable_fscache shall be used + * to mark sections of git code that extensively read from the file system + * without modifying anything. Implementations can use this to cache e.g. stat + * data or even file content without the need to synchronize with the file + * system. + */ +#ifndef enable_fscache +#define enable_fscache(x) /* noop */ +#endif + extern int cmd_main(int, const char **); /* diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index d13f02da95..fc00d5946a 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -431,6 +431,7 @@ END sub safe_pipe_capture { my @output; if (my $pid = open my $child, '-|') { + binmode($child, ":crlf"); @output = (<$child>); close $child or die join(' ',@_).": $! $?"; } else { diff --git a/http.c b/http.c index 4162860ee3..0ebf8f77a6 100644 --- a/http.c +++ b/http.c @@ -155,6 +155,16 @@ static struct active_request_slot *active_queue_head; static char *cached_accept_language; +static char *http_ssl_backend; + +static int http_schannel_check_revoke = 1; +/* + * With the backend being set to `schannel`, setting sslCAinfo would override + * the Certificate Store in cURL v7.60.0 and later, which is not what we want + * by default. + */ +static int http_schannel_use_ssl_cainfo; + size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; @@ -302,6 +312,22 @@ static int http_options(const char *var, const char *value, void *cb) curl_ssl_try = git_config_bool(var, value); return 0; } + if (!strcmp("http.sslbackend", var)) { + free(http_ssl_backend); + http_ssl_backend = xstrdup_or_null(value); + return 0; + } + + if (!strcmp("http.schannelcheckrevoke", var)) { + http_schannel_check_revoke = git_config_bool(var, value); + return 0; + } + + if (!strcmp("http.schannelusesslcainfo", var)) { + http_schannel_use_ssl_cainfo = git_config_bool(var, value); + return 0; + } + if (!strcmp("http.minsessions", var)) { min_curl_sessions = git_config_int(var, value); #ifndef USE_CURL_MULTI @@ -803,6 +829,16 @@ static CURL *get_curl_handle(void) } #endif + if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) && + !http_schannel_check_revoke) { +#if LIBCURL_VERSION_NUM >= 0x072c00 + curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE); +#else + warning("CURLSSLOPT_NO_REVOKE not applied to curl SSL options because\n" + "your curl version is too old (>= 7.44.0)"); +#endif + } + if (http_proactive_auth) init_curl_http_auth(result); @@ -844,7 +880,13 @@ static CURL *get_curl_handle(void) if (ssl_pinnedkey != NULL) curl_easy_setopt(result, CURLOPT_PINNEDPUBLICKEY, ssl_pinnedkey); #endif - if (ssl_cainfo != NULL) + if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) && + !http_schannel_use_ssl_cainfo) { + curl_easy_setopt(result, CURLOPT_CAINFO, NULL); +#if LIBCURL_VERSION_NUM >= 0x073400 + curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, NULL); +#endif + } else if (ssl_cainfo != NULL) curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) { @@ -995,6 +1037,33 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) git_config(urlmatch_config_entry, &config); free(normalized_url); +#if LIBCURL_VERSION_NUM >= 0x073800 + if (http_ssl_backend) { + const curl_ssl_backend **backends; + struct strbuf buf = STRBUF_INIT; + int i; + + switch (curl_global_sslset(-1, http_ssl_backend, &backends)) { + case CURLSSLSET_UNKNOWN_BACKEND: + strbuf_addf(&buf, _("Unsupported SSL backend '%s'. " + "Supported SSL backends:"), + http_ssl_backend); + for (i = 0; backends[i]; i++) + strbuf_addf(&buf, "\n\t%s", backends[i]->name); + die("%s", buf.buf); + case CURLSSLSET_NO_BACKENDS: + die(_("Could not set SSL backend to '%s': " + "cURL was built without SSL backends"), + http_ssl_backend); + case CURLSSLSET_TOO_LATE: + die(_("Could not set SSL backend to '%s': already set"), + http_ssl_backend); + case CURLSSLSET_OK: + break; /* Okay! */ + } + } +#endif + if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) die("curl_global_init failed"); diff --git a/pack-objects.c b/pack-objects.c index 6ef87e5683..f73f609884 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -148,6 +148,9 @@ void prepare_packing_data(struct packing_data *pdata) 1U << OE_SIZE_BITS); pdata->oe_delta_size_limit = git_env_ulong("GIT_TEST_OE_DELTA_SIZE", 1UL << OE_DELTA_SIZE_BITS); +#ifndef NO_PTHREADS + pthread_mutex_init(&pdata->lock, NULL); +#endif } struct object_entry *packlist_alloc(struct packing_data *pdata, diff --git a/pack-objects.h b/pack-objects.h index 62806ccc39..c34036166a 100644 --- a/pack-objects.h +++ b/pack-objects.h @@ -356,7 +356,7 @@ static inline unsigned long oe_delta_size(struct packing_data *pack, return e->delta_size_; /* - * pack->detla_size[] can't be NULL because oe_set_delta_size() + * pack->delta_size[] can't be NULL because oe_set_delta_size() * must have been called when a new delta is saved with * oe_set_delta(). * If oe_delta() returns NULL (i.e. default state, which means diff --git a/path.c b/path.c index 34f0f98349..a72abf0e1f 100644 --- a/path.c +++ b/path.c @@ -11,6 +11,7 @@ #include "path.h" #include "packfile.h" #include "object-store.h" +#include "exec-cmd.h" static int get_st_mode_bits(const char *path, int *mode) { @@ -709,6 +710,10 @@ char *expand_user_path(const char *path, int real_home) if (path == NULL) goto return_null; +#ifdef __MINGW32__ + if (path[0] == '/') + return system_path(path + 1); +#endif if (path[0] == '~') { const char *first_slash = strchrnul(path, '/'); const char *username = path + 1; diff --git a/preload-index.c b/preload-index.c index 71cd2437a3..9c22a0705f 100644 --- a/preload-index.c +++ b/preload-index.c @@ -93,6 +93,7 @@ static void preload_index(struct index_state *index, offset = 0; work = DIV_ROUND_UP(index->cache_nr, threads); memset(&data, 0, sizeof(data)); + enable_fscache(1); for (i = 0; i < threads; i++) { struct thread_data *p = data+i; p->index = index; @@ -110,6 +111,7 @@ static void preload_index(struct index_state *index, die("unable to join threaded lstat"); } trace_performance_since(start, "preload index"); + enable_fscache(0); } #endif diff --git a/send-pack.c b/send-pack.c index e920ca57df..bf54499257 100644 --- a/send-pack.c +++ b/send-pack.c @@ -38,6 +38,16 @@ int option_parse_push_signed(const struct option *opt, die("bad %s argument: %s", opt->long_name, arg); } +static int config_use_sideband = 1; + +static int send_pack_config(const char *var, const char *value, void *unused) +{ + if (!strcmp("sendpack.sideband", var)) + config_use_sideband = git_config_bool(var, value); + + return 0; +} + static void feed_object(const struct object_id *oid, FILE *fh, int negative) { if (negative && !has_sha1_file(oid->hash)) @@ -392,6 +402,8 @@ int send_pack(struct send_pack_args *args, struct async demux; const char *push_cert_nonce = NULL; + git_config(send_pack_config, NULL); + /* Does the other end support the reporting? */ if (server_supports("report-status")) status_report = 1; @@ -399,7 +411,7 @@ int send_pack(struct send_pack_args *args, allow_deleting_refs = 1; if (server_supports("ofs-delta")) args->use_ofs_delta = 1; - if (server_supports("side-band-64k")) + if (config_use_sideband && server_supports("side-band-64k")) use_sideband = 1; if (server_supports("quiet")) quiet_supported = 1; diff --git a/setup.c b/setup.c index b24c811c1c..57ba506d2a 100644 --- a/setup.c +++ b/setup.c @@ -39,7 +39,7 @@ static int abspath_part_inside_repo(char *path) off = offset_1st_component(path); /* check if work tree is already the prefix */ - if (wtlen <= len && !strncmp(path, work_tree, wtlen)) { + if (wtlen <= len && !fspathncmp(path, work_tree, wtlen)) { if (path[wtlen] == '/') { memmove(path, path + wtlen + 1, len - wtlen); return 0; @@ -59,7 +59,7 @@ static int abspath_part_inside_repo(char *path) path++; if (*path == '/') { *path = '\0'; - if (strcmp(real_path(path0), work_tree) == 0) { + if (fspathcmp(real_path(path0), work_tree) == 0) { memmove(path0, path + 1, len - (path - path0)); return 0; } @@ -68,7 +68,7 @@ static int abspath_part_inside_repo(char *path) } /* check whole path */ - if (strcmp(real_path(path0), work_tree) == 0) { + if (fspathcmp(real_path(path0), work_tree) == 0) { *path0 = '\0'; return 0; } @@ -760,7 +760,7 @@ static const char *setup_discovered_git_dir(const char *gitdir, set_git_dir(gitdir); inside_git_dir = 0; inside_work_tree = 1; - if (offset == cwd->len) + if (offset >= cwd->len) return NULL; /* Make "offset" point past the '/' (already the case for root dirs) */ @@ -892,7 +892,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT); struct string_list ceiling_dirs = STRING_LIST_INIT_DUP; const char *gitdirenv; - int ceil_offset = -1, min_offset = has_dos_drive_prefix(dir->buf) ? 3 : 1; + int ceil_offset = -1, min_offset = offset_1st_component(dir->buf); dev_t current_device = 0; int one_filesystem = 1; @@ -920,6 +920,12 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, if (ceil_offset < 0) ceil_offset = min_offset - 2; + if (min_offset && min_offset == dir->len && + !is_dir_sep(dir->buf[min_offset - 1])) { + strbuf_addch(dir, '/'); + min_offset++; + } + /* * Test in the following order (relative to the dir): * - .git (file containing "gitdir: ") diff --git a/t/.gitignore b/t/.gitignore index 348715f0e4..91cf5772fe 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -2,3 +2,4 @@ /test-results /.prove /chainlinttmp +/out/ diff --git a/t/helper/test-date.c b/t/helper/test-date.c index a0837371ab..792a805374 100644 --- a/t/helper/test-date.c +++ b/t/helper/test-date.c @@ -7,6 +7,7 @@ static const char *usage_msg = "\n" " test-tool date parse [date]...\n" " test-tool date approxidate [date]...\n" " test-tool date timestamp [date]...\n" +" test-tool date getnanos [start-nanos]\n" " test-tool date is64bit\n" " test-tool date time_t-is64bit\n"; @@ -82,6 +83,15 @@ static void parse_approx_timestamp(const char **argv, struct timeval *now) } } +static void getnanos(const char **argv, struct timeval *now) +{ + double seconds = getnanotime() / 1.0e9; + + if (*argv) + seconds -= strtod(*argv, NULL); + printf("%lf\n", seconds); +} + int cmd__date(int argc, const char **argv) { struct timeval now; @@ -108,6 +118,8 @@ int cmd__date(int argc, const char **argv) parse_approxidate(argv+1, &now); else if (!strcmp(*argv, "timestamp")) parse_approx_timestamp(argv+1, &now); + else if (!strcmp(*argv, "getnanos")) + getnanos(argv+1, &now); else if (!strcmp(*argv, "is64bit")) return sizeof(timestamp_t) == 8 ? 0 : 1; else if (!strcmp(*argv, "time_t-is64bit")) diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c index 2cc93bb69c..e1bc58b956 100644 --- a/t/helper/test-run-command.c +++ b/t/helper/test-run-command.c @@ -50,11 +50,58 @@ static int task_finished(int result, return 1; } +static int inherit_handle(const char *argv0) +{ + struct child_process cp = CHILD_PROCESS_INIT; + char path[PATH_MAX]; + int tmp; + + /* First, open an inheritable handle */ + xsnprintf(path, sizeof(path), "out-XXXXXX"); + tmp = xmkstemp(path); + + argv_array_pushl(&cp.args, + "test-tool", argv0, "inherited-handle-child", NULL); + cp.in = -1; + cp.no_stdout = cp.no_stderr = 1; + if (start_command(&cp) < 0) + die("Could not start child process"); + + /* Then close it, and try to delete it. */ + close(tmp); + if (unlink(path)) + die("Could not delete '%s'", path); + + if (close(cp.in) < 0 || finish_command(&cp) < 0) + die("Child did not finish"); + + return 0; +} + +static int inherit_handle_child(void) +{ + struct strbuf buf = STRBUF_INIT; + + if (strbuf_read(&buf, 0, 0) < 0) + die("Could not read stdin"); + printf("Received %s\n", buf.buf); + strbuf_release(&buf); + + return 0; +} + int cmd__run_command(int argc, const char **argv) { struct child_process proc = CHILD_PROCESS_INIT; int jobs; + if (argc < 2) + return 1; + if (!strcmp(argv[1], "inherited-handle")) + exit(inherit_handle(argv[0])); + if (!strcmp(argv[1], "inherited-handle-child")) + exit(inherit_handle_child()); + if (argc < 3) return 1; while (!strcmp(argv[1], "env")) { diff --git a/t/interop/i5500-git-daemon.sh b/t/interop/i5500-git-daemon.sh index 1daf69420b..4d22e42f84 100755 --- a/t/interop/i5500-git-daemon.sh +++ b/t/interop/i5500-git-daemon.sh @@ -37,5 +37,4 @@ test_expect_success "fetch with $VERSION_B" ' test_cmp expect actual ' -stop_git_daemon test_done diff --git a/t/lib-gettext.sh b/t/lib-gettext.sh index eec757f104..9eb160c997 100644 --- a/t/lib-gettext.sh +++ b/t/lib-gettext.sh @@ -10,7 +10,12 @@ GIT_TEXTDOMAINDIR="$GIT_BUILD_DIR/po/build/locale" GIT_PO_PATH="$GIT_BUILD_DIR/po" export GIT_TEXTDOMAINDIR GIT_PO_PATH -. "$GIT_BUILD_DIR"/git-sh-i18n +if test -n "$GIT_TEST_INSTALLED" +then + . "$(git --exec-path)"/git-sh-i18n +else + . "$GIT_BUILD_DIR"/git-sh-i18n +fi if test_have_prereq GETTEXT && ! test_have_prereq GETTEXT_POISON then diff --git a/t/lib-git-daemon.sh b/t/lib-git-daemon.sh index edbea2d986..a896af2284 100644 --- a/t/lib-git-daemon.sh +++ b/t/lib-git-daemon.sh @@ -13,7 +13,6 @@ # # test_expect_success ... # -# stop_git_daemon # test_done test_tristate GIT_TEST_GIT_DAEMON @@ -43,7 +42,7 @@ start_git_daemon() { mkdir -p "$GIT_DAEMON_DOCUMENT_ROOT_PATH" - trap 'code=$?; stop_git_daemon; (exit $code); die' EXIT + test_atexit 'stop_git_daemon' say >&3 "Starting git daemon ..." mkfifo git_daemon_output diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh index c27599474c..f4f5d7d296 100644 --- a/t/lib-git-p4.sh +++ b/t/lib-git-p4.sh @@ -74,15 +74,6 @@ cli="$TRASH_DIRECTORY/cli" git="$TRASH_DIRECTORY/git" pidfile="$TRASH_DIRECTORY/p4d.pid" -# Sometimes "prove" seems to hang on exit because p4d is still running -cleanup () { - if test -f "$pidfile" - then - kill -9 $(cat "$pidfile") 2>/dev/null && exit 255 - fi -} -trap cleanup EXIT - # git p4 submit generates a temp file, which will # not get cleaned up if the submission fails. Don't # clutter up /tmp on the test machine. @@ -141,6 +132,7 @@ start_p4d () { # p4d failed to start return 1 fi + test_atexit kill_p4d # build a p4 user so author@example.com has an entry p4_add_user author diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 850f651e4e..149f4ca8de 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -134,6 +134,7 @@ check_sub_test_lib_test_err () { ) } +cat >/dev/null <<\DDD test_expect_success 'pretend we have a fully passing test suite' " run_sub_test_lib_test full-pass '3 passing tests' <<-\\EOF && for i in 1 2 3 @@ -820,6 +821,25 @@ test_expect_success 'tests clean up even on failures' " > 1..2 EOF " +DDD + +test_expect_success 'test_atexit is run' " + test_must_fail run_sub_test_lib_test \ + atexit-cleanup 'Run atexit commands' -i <<-\\EOF && + test_expect_success 'tests clean up even after a failure' ' + > ../../clean-atexit && + test_atexit rm ../../clean-atexit && + > ../../also-clean-atexit && + test_atexit rm ../../also-clean-atexit && + > ../../dont-clean-atexit && + (exit 1) + ' + test_done + EOF + test_path_exists dont-clean-atexit && + test_path_is_missing clean-atexit && + test_path_is_missing also-clean-atexit +" ################################################################ # Basics of the basics diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 182da069f1..97a338e5cd 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -453,6 +453,18 @@ test_expect_success 're-init from a linked worktree' ' ) ' +test_expect_success MINGW 'core.hidedotfiles = false' ' + git config --global core.hidedotfiles false && + rm -rf newdir && + ( + sane_unset GIT_DIR GIT_WORK_TREE GIT_CONFIG && + mkdir newdir && + cd newdir && + git init + ) && + ! is_hidden newdir/.git +' + test_expect_success MINGW 'redirect std handles' ' GIT_REDIRECT_STDOUT=output.txt git rev-parse --git-dir && test .git = "$(cat output.txt)" && diff --git a/t/t0029-core-unsetenvvars.sh b/t/t0029-core-unsetenvvars.sh new file mode 100755 index 0000000000..24ce46a6ea --- /dev/null +++ b/t/t0029-core-unsetenvvars.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +test_description='test the Windows-only core.unsetenvvars setting' + +. ./test-lib.sh + +if ! test_have_prereq MINGW +then + skip_all='skipping Windows-specific tests' + test_done +fi + +test_expect_success 'setup' ' + mkdir -p "$TRASH_DIRECTORY/.git/hooks" && + write_script "$TRASH_DIRECTORY/.git/hooks/pre-commit" <<-\EOF + echo $HOBBES >&2 + EOF +' + +test_expect_success 'core.unsetenvvars works' ' + HOBBES=Calvin && + export HOBBES && + git commit --allow-empty -m with 2>err && + grep Calvin err && + git -c core.unsetenvvars=FINDUS,HOBBES,CALVIN \ + commit --allow-empty -m without 2>err && + ! grep Calvin err +' + +test_done diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index bc74073746..d74798526d 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -12,6 +12,10 @@ cat >hello-script <<-EOF cat hello-script EOF +test_expect_success MINGW 'subprocess inherits only std handles' ' + test-tool run-command inherited-handle +' + test_expect_success 'start_command reports ENOENT' ' test-tool run-command start-command-ENOENT ./does-not-exist ' diff --git a/t/t2029-checkout-long-paths.sh b/t/t2029-checkout-long-paths.sh new file mode 100755 index 0000000000..f30f8920ca --- /dev/null +++ b/t/t2029-checkout-long-paths.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +test_description='checkout long paths on Windows + +Ensures that Git for Windows can deal with long paths (>260) enabled via core.longpaths' + +. ./test-lib.sh + +if test_have_prereq !MINGW +then + skip_all='skipping MINGW specific long paths test' + test_done +fi + +test_expect_success setup ' + p=longpathxx && # -> 10 + p=$p$p$p$p$p && # -> 50 + p=$p$p$p$p$p && # -> 250 + + path=${p}/longtestfile && # -> 263 (MAX_PATH = 260) + + blob=$(echo foobar | git hash-object -w --stdin) && + + printf "100644 %s 0\t%s\n" "$blob" "$path" | + git update-index --add --index-info && + git commit -m initial -q +' + +test_expect_success 'checkout of long paths without core.longpaths fails' ' + git config core.longpaths false && + test_must_fail git checkout -f 2>error && + grep -q "Filename too long" error && + test ! -d longpa* +' + +test_expect_success 'checkout of long paths with core.longpaths works' ' + git config core.longpaths true && + git checkout -f && + test_path_is_file longpa*/longtestfile +' + +test_expect_success 'update of long paths' ' + echo frotz >>$(ls longpa*/longtestfile) && + echo $path > expect && + git ls-files -m > actual && + test_cmp expect actual && + git add $path && + git commit -m second && + git grep "frotz" HEAD -- $path +' + +test_expect_success cleanup ' + # bash cannot delete the trash dir if it contains a long path + # lets help cleaning up (unless in debug mode) + if test -z "$debug" + then + rm -rf longpa~1 + fi +' + +# check that the template used in the test won't be too long: +abspath="$(pwd)"/testdir +test ${#abspath} -gt 230 || +test_set_prereq SHORTABSPATH + +test_expect_success SHORTABSPATH 'clean up path close to MAX_PATH' ' + p=/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef && + p=y$p$p$p$p && + subdir="x$(echo "$p" | tail -c $((253 - ${#abspath})) - )" && + # Now, $abspath/$subdir has exactly 254 characters, and is inside CWD + p2="$abspath/$subdir" && + test 254 = ${#p2} && + + # Be careful to overcome path limitations of the MSys tools and split + # the $subdir into two parts. ($subdir2 has to contain 16 chars and a + # slash somewhere following; that is why we asked for abspath <= 230 and + # why we placed a slash near the end of the $subdir template.) + subdir2=${subdir#????????????????*/} && + subdir1=testdir/${subdir%/$subdir2} && + mkdir -p "$subdir1" && + i=0 && + # The most important case is when absolute path is 258 characters long, + # and that will be when i == 4. + while test $i -le 7 + do + mkdir -p $subdir2 && + touch $subdir2/one-file && + mv ${subdir2%%/*} "$subdir1/" && + subdir2=z${subdir2} && + i=$(($i+1)) || + exit 1 + done && + + # now check that git is able to clear the tree: + (cd testdir && + git init && + git config core.longpaths yes && + git clean -fdx) && + test ! -d "$subdir1" +' + +test_done diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 37729ba258..8ee4fc70ad 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -402,4 +402,11 @@ test_expect_success 'all statuses changed in folder if . is given' ' test $(git ls-files --stage | grep ^100755 | wc -l) -eq 0 ' +test_expect_success MINGW 'path is case-insensitive' ' + path="$(pwd)/BLUB" && + touch "$path" && + downcased="$(echo "$path" | tr A-Z a-z)" && + git add "$downcased" +' + test_done diff --git a/t/t5319-pack-large-objects.sh b/t/t5319-pack-large-objects.sh new file mode 100755 index 0000000000..a75eab87d3 --- /dev/null +++ b/t/t5319-pack-large-objects.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# +# Copyright (c) 2018 Johannes Schindelin +# + +test_description='git pack-object with "large" deltas + +' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-pack.sh + +# Two similar-ish objects that we have computed deltas between. +A=01d7713666f4de822776c7622c10f1b07de280dc +B=e68fe8129b546b101aee9510c5328e7f21ca1d18 + +test_expect_success 'setup' ' + clear_packs && + { + pack_header 2 && + pack_obj $A $B && + pack_obj $B + } >ab.pack && + pack_trailer ab.pack && + git index-pack --stdin "~" conversion for file - test_expect_success "fetch-pack --diag-url $p://$h/~$r" ' + test_expect_success !MINGW "fetch-pack --diag-url $p://$h/~$r" ' check_prot_path $p://$h/~$r $p "/~$r" ' + test_expect_success MINGW "fetch-pack --diag-url $p://$h/~$r" ' + check_prot_path $p://$h/~$r $p "//$h/~$r" + ' done # file without scheme for h in nohost nohost:12 [::1] [::1]:23 [ [:aa diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index 7466aad111..08f95c80a2 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -211,5 +211,4 @@ test_expect_success FAKENC 'hostname interpolation works after LF-stripping' ' test_cmp expect actual ' -stop_git_daemon test_done diff --git a/t/t5580-unc-paths.sh b/t/t5580-unc-paths.sh index 254fefccde..f9f0ef6b2d 100755 --- a/t/t5580-unc-paths.sh +++ b/t/t5580-unc-paths.sh @@ -17,14 +17,11 @@ fi UNCPATH="$(winpwd)" case "$UNCPATH" in [A-Z]:*) + WITHOUTDRIVE="${UNCPATH#?:}" # Use administrative share e.g. \\localhost\C$\git-sdk-64\usr\src\git # (we use forward slashes here because MSYS2 and Git accept them, and # they are easier on the eyes) - UNCPATH="//localhost/${UNCPATH%%:*}\$/${UNCPATH#?:}" - test -d "$UNCPATH" || { - skip_all='could not access administrative share; skipping' - test_done - } + UNCPATH="//localhost/${UNCPATH%%:*}\$$WITHOUTDRIVE" ;; *) skip_all='skipping UNC path tests, cannot determine current path as UNC' @@ -32,6 +29,18 @@ case "$UNCPATH" in ;; esac +test_expect_success 'clone into absolute path lacking a drive prefix' ' + USINGBACKSLASHES="$(echo "$WITHOUTDRIVE"/without-drive-prefix | + tr / \\\\)" && + git clone . "$USINGBACKSLASHES" && + test -f without-drive-prefix/.git/HEAD +' + +test -d "$UNCPATH" || { + skip_all='could not access administrative share; skipping' + test_done +} + test_expect_success setup ' test_commit initial ' @@ -74,4 +83,16 @@ test_expect_success MINGW 'remote nick cannot contain backslashes' ' test_i18ngrep ! "unable to access" err ' +test_expect_success 'unc alternates' ' + tree="$(git rev-parse HEAD:)" && + mkdir test-unc-alternate && + ( + cd test-unc-alternate && + git init && + test_must_fail git show $tree && + echo "$UNCPATH/.git/objects" >.git/objects/info/alternates && + git show $tree + ) +' + test_done diff --git a/t/t5607-clone-bundle.sh b/t/t5607-clone-bundle.sh index 348d9b3bc7..f84b875950 100755 --- a/t/t5607-clone-bundle.sh +++ b/t/t5607-clone-bundle.sh @@ -71,4 +71,8 @@ test_expect_success 'prerequisites with an empty commit message' ' git bundle verify bundle ' +test_expect_success 'try to create a bundle with empty ref count' ' + test_expect_code 1 git bundle create foobar.bundle master..master +' + test_done diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh index aaaa722cca..18cf138343 100755 --- a/t/t5801-remote-helpers.sh +++ b/t/t5801-remote-helpers.sh @@ -228,7 +228,7 @@ test_expect_success 'push update refs failure' ' echo "update fail" >>file && git commit -a -m "update fail" && git rev-parse --verify testgit/origin/heads/update >expect && - test_expect_code 1 env GIT_REMOTE_TESTGIT_FAILURE="non-fast forward" \ + test_must_fail env GIT_REMOTE_TESTGIT_FAILURE="non-fast forward" \ git push origin update && git rev-parse --verify testgit/origin/heads/update >actual && test_cmp expect actual diff --git a/t/t7108-reset-stdin.sh b/t/t7108-reset-stdin.sh new file mode 100755 index 0000000000..b7cbcbf869 --- /dev/null +++ b/t/t7108-reset-stdin.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test_description='reset --stdin' + +. ./test-lib.sh + +test_expect_success 'reset --stdin' ' + test_commit hello && + git rm hello.t && + test -z "$(git ls-files hello.t)" && + echo hello.t | git reset --stdin && + test hello.t = "$(git ls-files hello.t)" +' + +test_expect_success 'reset --stdin -z' ' + test_commit world && + git rm hello.t world.t && + test -z "$(git ls-files hello.t world.t)" && + printf world.tQworld.tQhello.tQ | q_to_nul | git reset --stdin -z && + printf "hello.t\nworld.t\n" >expect && + git ls-files >actual && + test_cmp expect actual +' + +test_expect_success '--stdin requires --mixed' ' + echo hello.t >list && + test_must_fail git reset --soft --stdin = 110) +longpath36=0123456789abcdefghijklmnopqrstuvwxyz +longpath180=$longpath36$longpath36$longpath36$longpath36$longpath36 + +# the git database must fit within PATH_MAX, which limits the submodule name +# to PATH_MAX - len(pwd) - ~90 (= len("/objects//") + 40-byte sha1 + some +# overhead from the test case) +pwd=$(pwd) +pwdlen=$(echo "$pwd" | wc -c) +longpath=$(echo $longpath180 | cut -c 1-$((170-$pwdlen))) + +test_expect_success 'submodule with a long path' ' + git init --bare remote && + test_create_repo bundle1 && + ( + cd bundle1 && + test_commit "shoot" && + git rev-parse --verify HEAD >../expect + ) && + mkdir home && + ( + cd home && + git clone ../remote test && + cd test && + git submodule add ../bundle1 $longpath && + test_commit "sogood" && + ( + cd $longpath && + git rev-parse --verify HEAD >actual && + test_cmp ../../../expect actual + ) && + git push origin master + ) && + mkdir home2 && + ( + cd home2 && + git clone ../remote test && + cd test && + git checkout master && + git submodule update --init && + ( + cd $longpath && + git rev-parse --verify HEAD >actual && + test_cmp ../../../expect actual + ) + ) +' + +test_expect_success 'recursive submodule with a long path' ' + git init --bare super && + test_create_repo child && + ( + cd child && + test_commit "shoot" && + git rev-parse --verify HEAD >../expect + ) && + test_create_repo parent && + ( + cd parent && + git submodule add ../child $longpath && + test_commit "aim" + ) && + mkdir home3 && + ( + cd home3 && + git clone ../super test && + cd test && + git submodule add ../parent foo && + git submodule update --init --recursive && + test_commit "sogood" && + ( + cd foo/$longpath && + git rev-parse --verify HEAD >actual && + test_cmp ../../../../expect actual + ) && + git push origin master + ) && + mkdir home4 && + ( + cd home4 && + git clone ../super test --recursive && + ( + cd test/foo/$longpath && + git rev-parse --verify HEAD >actual && + test_cmp ../../../../expect actual + ) + ) +' + +test_done diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 6a392e87bc..6d6d5050a2 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -556,4 +556,15 @@ test_expect_success 'merge commit gets exported with --import-marks' ' ) ' +cat > expected << EOF +reset refs/heads/master +from $(git rev-parse master) + +EOF + +test_expect_failure 'refs are updated even if no commits need to be exported' ' + git fast-export master..master > actual && + test_cmp expected actual +' + test_done diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh index 729cd25770..5856563068 100755 --- a/t/t9800-git-p4-basic.sh +++ b/t/t9800-git-p4-basic.sh @@ -326,8 +326,4 @@ test_expect_success 'submit from worktree' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh index 6a86d6996b..50013132c8 100755 --- a/t/t9801-git-p4-branch.sh +++ b/t/t9801-git-p4-branch.sh @@ -610,8 +610,4 @@ test_expect_success 'Update a file in git side and submit to P4 using client vie ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh index 9978352d78..94edebe272 100755 --- a/t/t9802-git-p4-filetype.sh +++ b/t/t9802-git-p4-filetype.sh @@ -333,8 +333,4 @@ test_expect_success SYMLINKS 'empty symlink target' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9803-git-p4-shell-metachars.sh b/t/t9803-git-p4-shell-metachars.sh index d5c3675100..2913277013 100755 --- a/t/t9803-git-p4-shell-metachars.sh +++ b/t/t9803-git-p4-shell-metachars.sh @@ -105,8 +105,4 @@ test_expect_success 'branch with shell char' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9804-git-p4-label.sh b/t/t9804-git-p4-label.sh index e30f80e617..3236457106 100755 --- a/t/t9804-git-p4-label.sh +++ b/t/t9804-git-p4-label.sh @@ -108,8 +108,4 @@ test_expect_failure 'two labels on the same changelist' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9805-git-p4-skip-submit-edit.sh b/t/t9805-git-p4-skip-submit-edit.sh index 5fbf904dc8..90ef647db7 100755 --- a/t/t9805-git-p4-skip-submit-edit.sh +++ b/t/t9805-git-p4-skip-submit-edit.sh @@ -98,8 +98,4 @@ test_expect_success 'no config, edited' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9806-git-p4-options.sh b/t/t9806-git-p4-options.sh index 3f5291b857..4e794a01bf 100755 --- a/t/t9806-git-p4-options.sh +++ b/t/t9806-git-p4-options.sh @@ -300,9 +300,4 @@ test_expect_success 'use --git-dir option and GIT_DIR' ' test_path_is_file "$git"/cli_file2.t ' - -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 2325599ee6..488d916c10 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -542,8 +542,4 @@ test_expect_success 'submit --update-shelve' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9808-git-p4-chdir.sh b/t/t9808-git-p4-chdir.sh index 11d2b5102c..58a9b3b71e 100755 --- a/t/t9808-git-p4-chdir.sh +++ b/t/t9808-git-p4-chdir.sh @@ -83,8 +83,4 @@ test_expect_success SYMLINKS 'p4 client root symlink should stay symbolic' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9809-git-p4-client-view.sh b/t/t9809-git-p4-client-view.sh index 897b3c3034..3cff1fce1b 100755 --- a/t/t9809-git-p4-client-view.sh +++ b/t/t9809-git-p4-client-view.sh @@ -836,8 +836,4 @@ test_expect_success 'quotes on both sides' ' git_verify "cdir 1/file11" "cdir 1/file12" ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9810-git-p4-rcs.sh b/t/t9810-git-p4-rcs.sh index cc53debe19..57b533dc6f 100755 --- a/t/t9810-git-p4-rcs.sh +++ b/t/t9810-git-p4-rcs.sh @@ -360,8 +360,4 @@ test_expect_failure 'Add keywords in git which do not match the default p4 value ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9811-git-p4-label-import.sh b/t/t9811-git-p4-label-import.sh index 602b0a5d5c..b70e81c3cd 100755 --- a/t/t9811-git-p4-label-import.sh +++ b/t/t9811-git-p4-label-import.sh @@ -259,9 +259,4 @@ test_expect_success 'importing labels with missing revisions' ' ) ' - -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9812-git-p4-wildcards.sh b/t/t9812-git-p4-wildcards.sh index 0206771fbb..254a7c2446 100755 --- a/t/t9812-git-p4-wildcards.sh +++ b/t/t9812-git-p4-wildcards.sh @@ -211,8 +211,4 @@ test_expect_success 'wildcard files requiring keyword scrub' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9813-git-p4-preserve-users.sh b/t/t9813-git-p4-preserve-users.sh index 783c6ad165..fd018c87a8 100755 --- a/t/t9813-git-p4-preserve-users.sh +++ b/t/t9813-git-p4-preserve-users.sh @@ -138,8 +138,4 @@ test_expect_success 'not preserving user with mixed authorship' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh index 60baa06e27..468767cbf4 100755 --- a/t/t9814-git-p4-rename.sh +++ b/t/t9814-git-p4-rename.sh @@ -242,8 +242,4 @@ test_expect_success P4D_HAVE_CONFIGURABLE_RUN_MOVE_ALLOW \ ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9815-git-p4-submit-fail.sh b/t/t9815-git-p4-submit-fail.sh index eaf03a6563..9779dc0d11 100755 --- a/t/t9815-git-p4-submit-fail.sh +++ b/t/t9815-git-p4-submit-fail.sh @@ -422,8 +422,4 @@ test_expect_success 'cleanup chmod after submit cancel' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9816-git-p4-locked.sh b/t/t9816-git-p4-locked.sh index d048bd33fa..932841003c 100755 --- a/t/t9816-git-p4-locked.sh +++ b/t/t9816-git-p4-locked.sh @@ -138,8 +138,4 @@ test_expect_failure 'move with lock taken' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9817-git-p4-exclude.sh b/t/t9817-git-p4-exclude.sh index aac568eadf..96d25f0c02 100755 --- a/t/t9817-git-p4-exclude.sh +++ b/t/t9817-git-p4-exclude.sh @@ -64,8 +64,4 @@ test_expect_success 'clone, then sync with exclude' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9818-git-p4-block.sh b/t/t9818-git-p4-block.sh index ce7cb22ad3..0db7ab9918 100755 --- a/t/t9818-git-p4-block.sh +++ b/t/t9818-git-p4-block.sh @@ -146,8 +146,4 @@ test_expect_success 'Clone repo with self-sizing block size' ' test_line_count \> 10 log ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9819-git-p4-case-folding.sh b/t/t9819-git-p4-case-folding.sh index d808c008c1..600ce1e0b0 100755 --- a/t/t9819-git-p4-case-folding.sh +++ b/t/t9819-git-p4-case-folding.sh @@ -53,8 +53,4 @@ test_expect_failure 'Clone UC repo with lc name' ' test_must_fail git p4 clone //depot/uc/... ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9820-git-p4-editor-handling.sh b/t/t9820-git-p4-editor-handling.sh index 3c22f74bd4..fa1bba1dd9 100755 --- a/t/t9820-git-p4-editor-handling.sh +++ b/t/t9820-git-p4-editor-handling.sh @@ -31,8 +31,4 @@ test_expect_success 'EDITOR with options' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9821-git-p4-path-variations.sh b/t/t9821-git-p4-path-variations.sh index 81e46acfa8..ef80f1690b 100755 --- a/t/t9821-git-p4-path-variations.sh +++ b/t/t9821-git-p4-path-variations.sh @@ -193,8 +193,4 @@ test_expect_success 'Add a new file and clone path with new file (ignorecase)' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9822-git-p4-path-encoding.sh b/t/t9822-git-p4-path-encoding.sh index c78477c19b..1bf7635016 100755 --- a/t/t9822-git-p4-path-encoding.sh +++ b/t/t9822-git-p4-path-encoding.sh @@ -67,8 +67,4 @@ test_expect_success 'Delete iso8859-1 encoded paths and clone' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9823-git-p4-mock-lfs.sh b/t/t9823-git-p4-mock-lfs.sh index 1f2dc369bf..88b76dc4d6 100755 --- a/t/t9823-git-p4-mock-lfs.sh +++ b/t/t9823-git-p4-mock-lfs.sh @@ -185,8 +185,4 @@ test_expect_success 'Run git p4 submit in repo configured with large file system ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9824-git-p4-git-lfs.sh b/t/t9824-git-p4-git-lfs.sh index ed80ca858c..a28dbbdd56 100755 --- a/t/t9824-git-p4-git-lfs.sh +++ b/t/t9824-git-p4-git-lfs.sh @@ -287,8 +287,4 @@ test_expect_success 'Add big files to repo and store files in LFS based on compr ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9825-git-p4-handle-utf16-without-bom.sh b/t/t9825-git-p4-handle-utf16-without-bom.sh index 1551845dc1..f049ff8229 100755 --- a/t/t9825-git-p4-handle-utf16-without-bom.sh +++ b/t/t9825-git-p4-handle-utf16-without-bom.sh @@ -43,8 +43,4 @@ test_expect_failure 'clone depot with invalid UTF-16 file in non-verbose mode' ' git p4 clone --dest="$git" //depot ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9826-git-p4-keep-empty-commits.sh b/t/t9826-git-p4-keep-empty-commits.sh index fa8b9daf1f..fd64afe064 100755 --- a/t/t9826-git-p4-keep-empty-commits.sh +++ b/t/t9826-git-p4-keep-empty-commits.sh @@ -127,8 +127,4 @@ test_expect_success 'Clone repo subdir with all history' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9827-git-p4-change-filetype.sh b/t/t9827-git-p4-change-filetype.sh index 7433998f47..d3670bd7a2 100755 --- a/t/t9827-git-p4-change-filetype.sh +++ b/t/t9827-git-p4-change-filetype.sh @@ -59,8 +59,4 @@ test_expect_success SYMLINKS 'change symbolic link to file' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9828-git-p4-map-user.sh b/t/t9828-git-p4-map-user.sh index e20395c89f..ca6c2942bd 100755 --- a/t/t9828-git-p4-map-user.sh +++ b/t/t9828-git-p4-map-user.sh @@ -54,8 +54,4 @@ test_expect_success 'Clone repo root path with all history' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9829-git-p4-jobs.sh b/t/t9829-git-p4-jobs.sh index 971aeeea1f..88cfb1fcd3 100755 --- a/t/t9829-git-p4-jobs.sh +++ b/t/t9829-git-p4-jobs.sh @@ -92,8 +92,4 @@ test_expect_success 'check log message of changelist with more jobs' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9830-git-p4-symlink-dir.sh b/t/t9830-git-p4-symlink-dir.sh index 2ad1b0810d..3fb6960c18 100755 --- a/t/t9830-git-p4-symlink-dir.sh +++ b/t/t9830-git-p4-symlink-dir.sh @@ -36,8 +36,4 @@ test_expect_success 'symlinked directory' ' ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9831-git-p4-triggers.sh b/t/t9831-git-p4-triggers.sh index be44c9751a..d743ca33ee 100755 --- a/t/t9831-git-p4-triggers.sh +++ b/t/t9831-git-p4-triggers.sh @@ -96,8 +96,4 @@ test_expect_success 'submit description with extra info lines from verbose p4 ch ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9832-unshelve.sh b/t/t9832-unshelve.sh index 48ec7679b8..b649d1b7c3 100755 --- a/t/t9832-unshelve.sh +++ b/t/t9832-unshelve.sh @@ -131,8 +131,4 @@ test_expect_success 'try to unshelve the change' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - test_done diff --git a/t/t9833-errors.sh b/t/t9833-errors.sh index 277d347012..1f3d879122 100755 --- a/t/t9833-errors.sh +++ b/t/t9833-errors.sh @@ -72,9 +72,4 @@ test_expect_success 'git operation with expired ticket' ' ) ' -test_expect_success 'kill p4d' ' - kill_p4d -' - - test_done diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 4207af4077..40ef84a97e 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -889,6 +889,35 @@ test_when_finished () { } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" } +# This function can be used to schedule some commands to be run +# unconditionally at the end of the test script, e.g. to stop a daemon: +# +# test_expect_success 'test git daemon' ' +# git daemon & +# daemon_pid=$! && +# test_atexit "kill $daemon_pid" && +# hello world +# ' + +test_atexit () { + # We cannot detect when we are in a subshell in general, but by + # doing so on Bash is better than nothing (the test will + # silently pass on other shells). + test "${BASH_SUBSHELL-0}" = 0 || + error "bug in test script: test_atexit does nothing in a subshell" + test_atexit_cleanup="{ $* + } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_atexit_cleanup" +} + +test_atexit_handler () { + test : != "$test_atexit_cleanup" || return 0 + + setup_malloc_check + test_eval_ "$test_atexit_cleanup" + test_atexit_cleanup=: + teardown_malloc_check +} + # Most tests can use the created repository, but some may need to create more. # Usage: test_create_repo test_create_repo () { @@ -898,7 +927,8 @@ test_create_repo () { mkdir -p "$repo" ( cd "$repo" || error "Cannot setup test environment" - "$GIT_EXEC_PATH/git-init" "--template=$GIT_BUILD_DIR/templates/blt/" >&3 2>&4 || + "${GIT_TEST_INSTALLED:-$GIT_EXEC_PATH}/git$X" init \ + "--template=$GIT_BUILD_DIR/templates/blt/" >&3 2>&4 || error "cannot run git init -- have you built things yet?" mv .git/hooks .git/hooks-disabled ) || exit diff --git a/t/test-lib.sh b/t/test-lib.sh index 44288cbb59..4461ea4307 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -49,18 +49,23 @@ export ASAN_OPTIONS : ${LSAN_OPTIONS=abort_on_error=1} export LSAN_OPTIONS +if test ! -f "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS +then + echo >&2 'error: GIT-BUILD-OPTIONS missing (has Git been built?).' + exit 1 +fi +. "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS +export PERL_PATH SHELL_PATH + ################################################################ # It appears that people try to run tests without building... -"$GIT_BUILD_DIR/git" >/dev/null +test -n "$GIT_TEST_INSTALLED" || "$GIT_BUILD_DIR/git$X" >/dev/null || if test $? != 1 then echo >&2 'error: you do not seem to have built git yet.' exit 1 fi -. "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS -export PERL_PATH SHELL_PATH - # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too. case "$GIT_TEST_TEE_STARTED, $* " in @@ -85,6 +90,13 @@ done,*) test "$(cat "$BASE.exit")" = 0 exit ;; +*' --write-junit-xml '*) + # record how to call this script *with* --verbose-log, in case + # we encounter a breakage + junit_rerun_options_sq="$(printf '%s\n' "$0" --verbose-log -x "$@" | + sed -e "s/'/'\\\\''/g" -e "s/^/'/" -e "s/\$/'/" | + tr '\012' ' ')" + ;; esac # For repeatability, reset the environment to known value. @@ -288,6 +300,9 @@ do --verbose-log) verbose_log=t shift ;; + --write-junit-xml) + write_junit_xml=t + shift ;; *) echo "error: unknown test option '$1'" >&2; exit 1 ;; esac @@ -410,6 +425,7 @@ test_external_has_tap=0 die () { code=$? + test_atexit_handler || code=$? if test -n "$GIT_EXIT_OK" then exit $code @@ -431,11 +447,48 @@ trap 'exit $?' INT # the test_expect_* functions instead. test_ok_ () { + if test -n "$write_junit_xml" + then + write_junit_xml_testcase "$*" + fi test_success=$(($test_success + 1)) say_color "" "ok $test_count - $@" } test_failure_ () { + if test -n "$write_junit_xml" + then + if test -z "$GIT_TEST_TEE_OUTPUT_FILE" + then + # clean up + test_atexit_handler + + # re-run with --verbose-log + echo "# Re-running: $junit_rerun_options_sq" >&2 + + cd "$TEST_DIRECTORY" && + eval "${TEST_SHELL_PATH}" "$junit_rerun_options_sq" \ + >/dev/null 2>&1 + status=$? + + say_color "" "$(test 0 = $status || + echo "not ")ok $test_count - (re-ran with trace)" + say "1..$test_count" + GIT_EXIT_OK=t + exit $status + fi + + junit_insert="" + junit_insert="$junit_insert $(xml_attr_encode \ + "$(cat "$GIT_TEST_TEE_OUTPUT_FILE")")" + >"$GIT_TEST_TEE_OUTPUT_FILE" + junit_insert="$junit_insert" + junit_insert="$junit_insert$(xml_attr_encode \ + "$(cat "$GIT_TEST_TEE_OUTPUT_FILE.err")")" + >"$GIT_TEST_TEE_OUTPUT_FILE.err" + write_junit_xml_testcase "$1" " $junit_insert" + fi test_failure=$(($test_failure + 1)) say_color error "not ok $test_count - $1" shift @@ -444,11 +497,19 @@ test_failure_ () { } test_known_broken_ok_ () { + if test -n "$write_junit_xml" + then + write_junit_xml_testcase "$* (breakage fixed)" + fi test_fixed=$(($test_fixed+1)) say_color error "ok $test_count - $@ # TODO known breakage vanished" } test_known_broken_failure_ () { + if test -n "$write_junit_xml" + then + write_junit_xml_testcase "$* (known breakage)" + fi test_broken=$(($test_broken+1)) say_color warn "not ok $test_count - $@ # TODO known breakage" } @@ -706,6 +767,17 @@ test_start_ () { test_count=$(($test_count+1)) maybe_setup_verbose maybe_setup_valgrind + if test -n "$write_junit_xml" + then + junit_start=$(test-tool date getnanos) + + # append to future ; truncate output + test -z "$GIT_TEST_TEE_OUTPUT_FILE" || { + cat "$GIT_TEST_TEE_OUTPUT_FILE" \ + >>"$GIT_TEST_TEE_OUTPUT_FILE.err" + >"$GIT_TEST_TEE_OUTPUT_FILE" + } + fi } test_finish_ () { @@ -743,6 +815,13 @@ test_skip () { case "$to_skip" in t) + if test -n "$write_junit_xml" + then + message="$(xml_attr_encode "$skipped_reason")" + write_junit_xml_testcase "$1" \ + " " + fi + say_color skip >&3 "skipping test: $@" say_color skip "ok $test_count # skip $1 ($skipped_reason)" : true @@ -758,9 +837,61 @@ test_at_end_hook_ () { : } +write_junit_xml () { + case "$1" in + --truncate) + >"$junit_xml_path" + junit_have_testcase= + shift + ;; + esac + printf '%s\n' "$@" >>"$junit_xml_path" +} + +xml_attr_encode () { + # We do not translate CR to because BSD sed does not handle + # \r in the regex. In practice, the output should not even have any + # carriage returns. + printf '%s\n' "$@" | + sed -e 's/&/\&/g' -e "s/'/\'/g" -e 's/"/\"/g' \ + -e 's//\>/g' \ + -e 's/ /\ /g' -e 's/$/\ /' -e '$s/ $//' | + tr -d '\012\015' +} + +write_junit_xml_testcase () { + junit_attrs="name=\"$(xml_attr_encode "$this_test.$test_count $1")\"" + shift + junit_attrs="$junit_attrs classname=\"$this_test\"" + junit_attrs="$junit_attrs time=\"$(test-tool \ + date getnanos $junit_start)\"" + write_junit_xml "$(printf '%s\n' \ + " " "$@" " ")" + junit_have_testcase=t +} + +test_atexit_cleanup=: test_done () { GIT_EXIT_OK=t + test -n "$immediate" || test_atexit_handler + + if test -n "$write_junit_xml" && test -n "$junit_xml_path" + then + test -n "$junit_have_testcase" || { + junit_start=$(test-tool date getnanos) + write_junit_xml_testcase "all tests skipped" + } + + # adjust the overall time + junit_time=$(test-tool date getnanos $junit_suite_start) + sed "s/]*/& time=\"$junit_time\"/" \ + <"$junit_xml_path" >"$junit_xml_path.new" + mv "$junit_xml_path.new" "$junit_xml_path" + + write_junit_xml " " "" + fi + if test -z "$HARNESS_ACTIVE" then test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results" @@ -926,7 +1057,7 @@ elif test -n "$GIT_TEST_INSTALLED" then GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) || error "Cannot run git from $GIT_TEST_INSTALLED." - PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR:$PATH + PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR/t/helper:$PATH GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH} else # normal case, use ../bin-wrappers only unless $with_dashes: git_bin_dir="$GIT_BUILD_DIR/bin-wrappers" @@ -996,6 +1127,7 @@ then else mkdir -p "$TRASH_DIRECTORY" fi + # Use -P to resolve symlinks in our working directory so that the cwd # in subprocesses like git equals our $PWD (for pathname comparisons). cd -P "$TRASH_DIRECTORY" || exit 1 @@ -1009,6 +1141,19 @@ then test_done fi +if test -n "$write_junit_xml" +then + junit_xml_dir="$TEST_OUTPUT_DIRECTORY/out" + mkdir -p "$junit_xml_dir" + junit_xml_base=${0##*/} + junit_xml_path="$junit_xml_dir/TEST-${junit_xml_base%.sh}.xml" + junit_attrs="name=\"${junit_xml_base%.sh}\"" + junit_attrs="$junit_attrs timestamp=\"$(TZ=UTC \ + date +%Y-%m-%dT%H:%M:%S)\"" + write_junit_xml --truncate "" " " + junit_suite_start=$(test-tool date getnanos) +fi + # Provide an implementation of the 'yes' utility yes () { if test $# = 0 diff --git a/transport-helper.c b/transport-helper.c index 501a41115e..2962c89ee5 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -16,6 +16,8 @@ #include "protocol.h" static int debug; +/* TODO: put somewhere sensible, e.g. git_transport_options? */ +static int auto_gc = 1; struct helper_data { const char *name; @@ -460,10 +462,25 @@ static int get_exporter(struct transport *transport, for (i = 0; i < revlist_args->nr; i++) argv_array_push(&fastexport->args, revlist_args->items[i].string); + argv_array_push(&fastexport->args, "--"); + fastexport->git_cmd = 1; return start_command(fastexport); } +static void check_helper_status(struct helper_data *data) +{ + int pid, status; + + pid = waitpid(data->helper->pid, &status, WNOHANG); + if (pid < 0) + die("Could not retrieve status of remote helper '%s'", + data->name); + if (pid > 0 && WIFEXITED(status)) + die("Remote helper '%s' died with %d", + data->name, WEXITSTATUS(status)); +} + static int fetch_with_import(struct transport *transport, int nr_heads, struct ref **to_fetch) { @@ -500,6 +517,7 @@ static int fetch_with_import(struct transport *transport, if (finish_command(&fastimport)) die(_("error while running fast-import")); + check_helper_status(data); /* * The fast-import stream of a remote helper that advertises @@ -533,6 +551,12 @@ static int fetch_with_import(struct transport *transport, } } strbuf_release(&buf); + if (auto_gc) { + const char *argv_gc_auto[] = { + "gc", "--auto", "--quiet", NULL, + }; + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + } return 0; } @@ -987,6 +1011,7 @@ static int push_refs_with_export(struct transport *transport, if (finish_command(&exporter)) die(_("error while running fast-export")); + check_helper_status(data); if (push_update_refs_status(data, remote_refs, flags)) return 1;