diff --git a/.editorconfig b/.editorconfig index d4b419012..2230fd860 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,15 +1,21 @@ ; Check http://editorconfig.org/ for more informations -; Top-most EditorConfig file root = true -; tab indentation [*] indent_style = tab +tab_width = 8 trim_trailing_whitespace = true insert_final_newline = true -; 4-column space indentation +[*.yml] +indent_style = space +indent_size = 2 + [*.md] indent_style = space indent_size = 4 trim_trailing_whitespace = false + +[*.py] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore index 5991a41b9..dec3dca06 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ tags mkmf.log *.profdata *.profraw +CMakeSettings.json +.vs diff --git a/AUTHORS b/AUTHORS index 458ff06c3..784bab3ee 100644 --- a/AUTHORS +++ b/AUTHORS @@ -23,6 +23,7 @@ Dmitry Kovega Emeric Fermas Emmanuel Rodriguez Eric Myhre +Erik Aigner Florian Forster Holger Weiss Ingmar Vanhassel diff --git a/CMakeLists.txt b/CMakeLists.txt index 88eb9ec10..826415893 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ # > cmake --build . --target install PROJECT(libgit2 C) -CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11) +CMAKE_MINIMUM_REQUIRED(VERSION 3.5.1) CMAKE_POLICY(SET CMP0015 NEW) IF(POLICY CMP0051) CMAKE_POLICY(SET CMP0051 NEW) @@ -36,6 +36,7 @@ INCLUDE(AddCFlagIfSupported) INCLUDE(FindPkgLibraries) INCLUDE(FindThreads) INCLUDE(FindStatNsec) +INCLUDE(GNUInstallDirs) INCLUDE(IdeSplitSources) INCLUDE(FeatureSummary) INCLUDE(EnableWarnings) @@ -50,21 +51,28 @@ OPTION(BUILD_EXAMPLES "Build library usage example apps" OFF) OPTION(BUILD_FUZZERS "Build the fuzz targets" OFF) OPTION(TAGS "Generate tags" OFF) OPTION(PROFILE "Generate profiling information" OFF) -OPTION(ENABLE_TRACE "Enables tracing support" OFF) +OPTION(ENABLE_TRACE "Enables tracing support" ON) OPTION(LIBGIT2_FILENAME "Name of the produced binary" OFF) - - SET(SHA1_BACKEND "CollisionDetection" CACHE STRING - "Backend to use for SHA1. One of Generic, OpenSSL, Win32, CommonCrypto, mbedTLS, CollisionDetection.") OPTION(USE_SSH "Link with libssh2 to enable SSH support" ON) OPTION(USE_HTTPS "Enable HTTPS support. Can be set to a specific backend" ON) +OPTION(USE_SHA1 "Enable SHA1. Can be set to CollisionDetection(ON)/HTTPS/Generic" ON) OPTION(USE_GSSAPI "Link with libgssapi for SPNEGO auth" OFF) OPTION(USE_STANDALONE_FUZZERS "Enable standalone fuzzers (compatible with gcc)" OFF) -OPTION(VALGRIND "Configure build for valgrind" OFF) -OPTION(USE_EXT_HTTP_PARSER "Use system HTTP_Parser if available" ON) +OPTION(USE_LEAK_CHECKER "Run tests with leak checker" OFF) OPTION(DEBUG_POOL "Enable debug pool allocator" OFF) OPTION(ENABLE_WERROR "Enable compilation with -Werror" OFF) OPTION(USE_BUNDLED_ZLIB "Use the bundled version of zlib" OFF) + SET(USE_HTTP_PARSER "" CACHE STRING "Specifies the HTTP Parser implementation; either system or builtin.") OPTION(DEPRECATE_HARD "Do not include deprecated functions in the library" OFF) + SET(REGEX_BACKEND "" CACHE STRING "Regular expression implementation. One of regcomp_l, pcre2, pcre, regcomp, or builtin.") + +IF (UNIX) + IF (NOT USE_HTTPS) + OPTION(USE_NTLMCLIENT "Enable NTLM support on Unix." OFF ) + ELSE() + OPTION(USE_NTLMCLIENT "Enable NTLM support on Unix." ON ) + ENDIF() +ENDIF() IF (UNIX AND NOT APPLE) OPTION(ENABLE_REPRODUCIBLE_BUILDS "Enable reproducible builds" OFF) @@ -103,8 +111,8 @@ STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_V STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_VERSION_REV "${GIT2_HEADER}") SET(LIBGIT2_VERSION_STRING "${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}.${LIBGIT2_VERSION_REV}") -FILE(STRINGS "${libgit2_SOURCE_DIR}/include/git2/version.h" GIT2_HEADER_SOVERSION REGEX "^#define LIBGIT2_SOVERSION [0-9]+$") -STRING(REGEX REPLACE "^.*LIBGIT2_SOVERSION ([0-9]+)$" "\\1" LIBGIT2_SOVERSION "${GIT2_HEADER_SOVERSION}") +FILE(STRINGS "${libgit2_SOURCE_DIR}/include/git2/version.h" GIT2_HEADER_SOVERSION REGEX "^#define LIBGIT2_SOVERSION \"([0-9.]+)\"$") +STRING(REGEX REPLACE "^.*LIBGIT2_SOVERSION \"([0-9.]+)\"$" "\\1" LIBGIT2_SOVERSION "${GIT2_HEADER_SOVERSION}") IF (DEPRECATE_HARD) ADD_DEFINITIONS(-DGIT_DEPRECATE_HARD) @@ -232,10 +240,22 @@ ELSE () ENABLE_WARNINGS(shift-count-overflow) ENABLE_WARNINGS(unused-const-variable) ENABLE_WARNINGS(unused-function) - ENABLE_WARNINGS(format) - ENABLE_WARNINGS(format-security) ENABLE_WARNINGS(int-conversion) - DISABLE_WARNINGS(documentation-deprecated-sync) + + # MinGW uses gcc, which expects POSIX formatting for printf, but + # uses the Windows C library, which uses its own format specifiers. + # Disable format specifier warnings. + IF(MINGW) + DISABLE_WARNINGS(format) + DISABLE_WARNINGS(format-security) + ELSE() + ENABLE_WARNINGS(format) + ENABLE_WARNINGS(format-security) + ENDIF() + + IF("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + DISABLE_WARNINGS(documentation-deprecated-sync) + ENDIF() IF (PROFILE) SET(CMAKE_C_FLAGS "-pg ${CMAKE_C_FLAGS}") @@ -243,6 +263,11 @@ ELSE () ENDIF () ENDIF() +# Ensure that MinGW provides the correct header files. +IF (WIN32 AND NOT CYGWIN) + ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0600) +ENDIF() + IF( NOT CMAKE_CONFIGURATION_TYPES ) # Build Debug by default IF (NOT CMAKE_BUILD_TYPE) @@ -310,10 +335,5 @@ IF(BUILD_FUZZERS) ADD_SUBDIRECTORY(fuzzers) ENDIF() -IF(CMAKE_VERSION VERSION_GREATER 3) - FEATURE_SUMMARY(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:") - FEATURE_SUMMARY(WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:") -ELSE() - PRINT_ENABLED_FEATURES() - PRINT_DISABLED_FEATURES() -ENDIF() +FEATURE_SUMMARY(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:") +FEATURE_SUMMARY(WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:") diff --git a/COPYING b/COPYING index da695ebdb..c0f61fb91 100644 --- a/COPYING +++ b/COPYING @@ -991,3 +991,31 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---------------------------------------------------------------------- + +The bundled wildmatch code is licensed under the BSD license: + +Copyright Rich Salz. +All rights reserved. + +Redistribution and use in any form are permitted provided that the +following restrictions are are met: + +1. Source distributions must retain this entire copyright notice + and comment. +2. Binary distributions must include the acknowledgement ``This + product includes software developed by Rich Salz'' in the + documentation or other materials provided with the + distribution. This must not be represented as an endorsement + or promotion without specific prior written permission. +3. The origin of this software must not be misrepresented, either + by explicit claim or by omission. Credits must appear in the + source and documentation. +4. Altered versions must be plainly marked as such in the source + and documentation and must not be misrepresented as being the + original software. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. diff --git a/README.md b/README.md index 3d05dab8e..b5f04403b 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,8 @@ libgit2 - the Git linkable library | Build Status | | | ------------ | - | | **master** branch CI builds | [![Azure Pipelines Build Status](https://dev.azure.com/libgit2/libgit2/_apis/build/status/libgit2?branchName=master)](https://dev.azure.com/libgit2/libgit2/_build/latest?definitionId=7&branchName=master) | +| **v0.99 branch** CI builds | [![Azure Pipelines Build Status](https://dev.azure.com/libgit2/libgit2/_apis/build/status/libgit2?branchName=maint/v0.99)](https://dev.azure.com/libgit2/libgit2/_build/latest?definitionId=7&branchName=maint/v0.99) | | **v0.28 branch** CI builds | [![Azure Pipelines Build Status](https://dev.azure.com/libgit2/libgit2/_apis/build/status/libgit2?branchName=maint/v0.28)](https://dev.azure.com/libgit2/libgit2/_build/latest?definitionId=7&branchName=maint/v0.28) | -| **v0.27 branch** CI builds | [![Azure Pipelines Build Status](https://dev.azure.com/libgit2/libgit2/_apis/build/status/libgit2?branchName=maint/v0.27)](https://dev.azure.com/libgit2/libgit2/_build/latest?definitionId=7&branchName=maint/v0.27) | -| **v0.26 branch** CI builds | [![Azure Pipelines Build Status](https://dev.azure.com/libgit2/libgit2/_apis/build/status/libgit2?branchName=maint/v0.26)](https://dev.azure.com/libgit2/libgit2/_build/latest?definitionId=7&branchName=maint/v0.26) | | **Nightly** builds | [![Azure Pipelines Build Status](https://libgit2.visualstudio.com/libgit2/_apis/build/status/nightly?branchName=master&label=Full+Build)](https://libgit2.visualstudio.com/libgit2/_build/latest?definitionId=9&branchName=master) [![Coverity Build Status](https://dev.azure.com/libgit2/libgit2/_apis/build/status/coverity?branchName=master&label=Coverity+Build)](https://dev.azure.com/libgit2/libgit2/_build/latest?definitionId=21?branchName=master) [![Coverity Scan Build Status](https://scan.coverity.com/projects/639/badge.svg)](https://scan.coverity.com/projects/639) | `libgit2` is a portable, pure C implementation of the Git core methods @@ -73,17 +72,17 @@ Quick Start 2. Create the cmake build environment: `cmake ..` 3. Build libgit2: `cmake --build .` -Trouble with these steps? Read our (troubleshooting guide)[docs/troubleshooting.md]. +Trouble with these steps? Read our [troubleshooting guide](docs/troubleshooting.md). More detailed build guidance is available below. Getting Help ============ -**Join us on Slack** +**Chat with us** -Visit [slack.libgit2.org](http://slack.libgit2.org/) to sign up, then join -us in `#libgit2`. If you prefer IRC, you can also point your client to our -slack channel once you've registered. +- via IRC: join [#libgit2](https://webchat.freenode.net/#libgit2) on Freenode +- via Slack: visit [slack.libgit2.org](http://slack.libgit2.org/) to sign up, + then join us in `#libgit2` **Getting Help** @@ -103,9 +102,7 @@ We ask that you not open a GitHub Issue for help, only for bug reports. **Reporting Security Issues** -In case you think to have found a security issue with libgit2, please do not -open a public issue. Instead, you can report the issue to the private mailing -list [security@libgit2.com](mailto:security@libgit2.com). +Please have a look at SECURITY.md. What It Can Do ============== @@ -250,9 +247,9 @@ For more advanced use or questions about CMake please read * Java * Jagged +* Javascript / WebAssembly ( browser and nodejs ) + * WASM-git * Julia - * LibGit2.jl + * LibGit2.jl * Lua * luagit2 * .NET @@ -359,7 +358,7 @@ Here are the bindings to libgit2 that are currently available: * Ruby * Rugged * Rust - * git2-rs + * git2-rs * Swift * SwiftGit2 * Vala diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..f98eebf50 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +## Supported Versions + +This project will always provide security fixes for the latest two released +versions. E.g. if the latest version is v0.28.x, then we will provide security +fixes for both v0.28.x and v0.27.y, but no later versions. + +## Reporting a Vulnerability + +In case you think to have found a security issue with libgit2, please do not +open a public issue. Instead, you can report the issue to the private mailing +list [security@libgit2.com](mailto:security@libgit2.com). We will acknowledge +receipt of your message in at most three days and try to clarify further steps. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a37319adf..20335b33a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,134 +6,169 @@ trigger: - maint/* jobs: -- job: linux_amd64_trusty_gcc_openssl - displayName: 'Linux (amd64; Trusty; GCC; OpenSSL)' +- job: linux_amd64_xenial_gcc_openssl + displayName: 'Linux (amd64; Xenial; GCC; OpenSSL)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: azure-pipelines/docker.yml parameters: - imageName: 'libgit2/trusty-amd64:latest' + docker: + image: xenial + base: ubuntu:xenial environmentVariables: | CC=gcc - CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON - LEAK_CHECK=valgrind + CMAKE_GENERATOR=Ninja + CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=builtin -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON + GITTEST_NEGOTIATE_PASSWORD=${{ variables.GITTEST_NEGOTIATE_PASSWORD }} -- job: linux_amd64_trusty_gcc_mbedtls - displayName: 'Linux (amd64; Trusty; GCC; mbedTLS)' +- job: linux_amd64_xenial_gcc_mbedtls + displayName: 'Linux (amd64; Xenial; GCC; mbedTLS)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: azure-pipelines/docker.yml parameters: - imageName: 'libgit2/trusty-amd64:latest' + docker: + image: xenial + base: ubuntu:xenial environmentVariables: | CC=gcc - CMAKE_OPTIONS=-DUSE_HTTPS=mbedTLS -DSHA1_BACKEND=mbedTLS -DDEPRECATE_HARD=ON - LEAK_CHECK=valgrind + CMAKE_GENERATOR=Ninja + CMAKE_OPTIONS=-DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON + GITTEST_NEGOTIATE_PASSWORD=${{ variables.GITTEST_NEGOTIATE_PASSWORD }} -- job: linux_amd64_trusty_clang_openssl - displayName: 'Linux (amd64; Trusty; Clang; OpenSSL)' +- job: linux_amd64_xenial_clang_openssl + displayName: 'Linux (amd64; Xenial; Clang; OpenSSL)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: azure-pipelines/docker.yml parameters: - imageName: 'libgit2/trusty-amd64:latest' + docker: + image: xenial + base: ubuntu:xenial environmentVariables: | CC=clang - CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON - LEAK_CHECK=valgrind + CMAKE_GENERATOR=Ninja + CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON + GITTEST_NEGOTIATE_PASSWORD=${{ variables.GITTEST_NEGOTIATE_PASSWORD }} -- job: linux_amd64_trusty_clang_mbedtls - displayName: 'Linux (amd64; Trusty; Clang; mbedTLS)' +- job: linux_amd64_xenial_clang_mbedtls + displayName: 'Linux (amd64; Xenial; Clang; mbedTLS)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: azure-pipelines/docker.yml parameters: - imageName: 'libgit2/trusty-amd64:latest' + docker: + image: xenial + base: ubuntu:xenial environmentVariables: | CC=clang - CMAKE_OPTIONS=-DUSE_HTTPS=mbedTLS -DSHA1_BACKEND=mbedTLS -DDEPRECATE_HARD=ON - LEAK_CHECK=valgrind + CMAKE_GENERATOR=Ninja + CMAKE_OPTIONS=-DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind -DUSE_GSSAPI=ON + GITTEST_NEGOTIATE_PASSWORD=${{ variables.GITTEST_NEGOTIATE_PASSWORD }} - job: macos - displayName: 'macOS' + displayName: 'macOS (amd64; 10.15)' pool: - vmImage: 'macOS 10.13' + vmImage: 'macOS-10.15' steps: - - bash: . '$(Build.SourcesDirectory)/ci/setup-osx.sh' + - bash: . '$(Build.SourcesDirectory)/azure-pipelines/setup-osx.sh' displayName: Setup - template: azure-pipelines/bash.yml parameters: environmentVariables: TMPDIR: $(Agent.TempDirectory) PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig - LEAK_CHECK: leaks - CMAKE_OPTIONS: -G Ninja -DDEPRECATE_HARD=ON + CMAKE_GENERATOR: Ninja + CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON SKIP_SSH_TESTS: true + GITTEST_NEGOTIATE_PASSWORD: ${{ variables.GITTEST_NEGOTIATE_PASSWORD }} - job: windows_vs_amd64 displayName: 'Windows (amd64; Visual Studio)' - pool: Hosted + pool: + vmImage: 'vs2017-win2016' steps: - - template: azure-pipelines/powershell.yml + - template: azure-pipelines/bash.yml parameters: environmentVariables: - CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013 Win64" -DDEPRECATE_HARD=ON + CMAKE_GENERATOR: Visual Studio 15 2017 + CMAKE_OPTIONS: -A x64 -DMSVC_CRTDBG=ON -DDEPRECATE_HARD=ON + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true - job: windows_vs_x86 displayName: 'Windows (x86; Visual Studio)' - pool: Hosted + pool: + vmImage: 'vs2017-win2016' steps: - - template: azure-pipelines/powershell.yml + - template: azure-pipelines/bash.yml parameters: environmentVariables: - CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013" -DDEPRECATE_HARD=ON + CMAKE_GENERATOR: Visual Studio 15 2017 + CMAKE_OPTIONS: -A Win32 -DMSVC_CRTDBG=ON -DDEPRECATE_HARD=ON -DUSE_SHA1=HTTPS + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true - job: windows_mingw_amd64 displayName: 'Windows (amd64; MinGW)' - pool: Hosted + pool: + vmImage: 'vs2017-win2016' steps: - - powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1' + - bash: . '$(Build.SourcesDirectory)\azure-pipelines\setup-mingw.sh' displayName: Setup env: TEMP: $(Agent.TempDirectory) ARCH: amd64 - - template: azure-pipelines/powershell.yml + - template: azure-pipelines/bash.yml parameters: environmentVariables: - CMAKE_OPTIONS: -G"MinGW Makefiles" -DDEPRECATE_HARD=ON - PATH: $(Agent.TempDirectory)\mingw64\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin + BUILD_PATH: $(Agent.TempDirectory)\mingw64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin + CMAKE_GENERATOR: MinGW Makefiles + CMAKE_OPTIONS: -DDEPRECATE_HARD=ON + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true - job: windows_mingw_x86 displayName: 'Windows (x86; MinGW)' - pool: Hosted + pool: + vmImage: 'vs2017-win2016' steps: - - powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1' + - bash: . '$(Build.SourcesDirectory)\azure-pipelines\setup-mingw.sh' displayName: Setup workingDirectory: '$(Build.BinariesDirectory)' env: TEMP: $(Agent.TempDirectory) ARCH: x86 - - template: azure-pipelines/powershell.yml + - template: azure-pipelines/bash.yml parameters: environmentVariables: - CMAKE_OPTIONS: -G"MinGW Makefiles" -DDEPRECATE_HARD=ON - PATH: $(Agent.TempDirectory)\mingw32\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin + BUILD_PATH: $(Agent.TempDirectory)\mingw32\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin + CMAKE_GENERATOR: MinGW Makefiles + CMAKE_OPTIONS: -DDEPRECATE_HARD=ON + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true - job: documentation displayName: 'Generate Documentation' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: + - script: | + cd $(Build.SourcesDirectory)/azure-pipelines/docker + docker build -t libgit2/docurium --build-arg BASE=ubuntu:trusty -f docurium . + displayName: 'Build Docker image' - script: | git config user.name 'Documentation Generation' - git config user.email 'noreply@libgit2.org' - docker run --rm -v /home/vsts/work/1/s:/src -w /src libgit2/docurium:test cm doc api.docurium + git config user.email 'libgit2@users.noreply.github.com' + git branch gh-pages origin/gh-pages + docker run --rm -v $(Build.SourcesDirectory):/home/libgit2/source -w /home/libgit2/source libgit2/docurium:latest cm doc api.docurium git checkout gh-pages cp -R * '$(Build.BinariesDirectory)' + displayName: 'Generate Documentation' - task: archivefiles@2 displayName: 'Archive Documentation' inputs: @@ -141,7 +176,15 @@ jobs: includeRootFolder: false archiveFile: '$(Build.ArtifactStagingDirectory)/api-documentation.zip' - task: publishbuildartifacts@1 - displayName: 'Upload Documentation' + displayName: 'Upload Documentation Artifact' inputs: pathToPublish: '$(Build.ArtifactStagingDirectory)' artifactName: 'docs' + - script: | + git remote -v + echo 'machine github.com' > ~/.netrc + echo 'login $(GITHUB_USERNAME)' >> ~/.netrc + echo 'password $(GITHUB_PAT)' >> ~/.netrc + git push origin gh-pages + displayName: 'Publish Documentation' + condition: and(eq(variables['Build.Repository.Name'], 'libgit2/libgit2'), eq(variables['Build.Reason'], 'IndividualCI')) diff --git a/azure-pipelines/bash.yml b/azure-pipelines/bash.yml index d776a3649..33a442b57 100644 --- a/azure-pipelines/bash.yml +++ b/azure-pipelines/bash.yml @@ -1,10 +1,10 @@ # These are the steps used for building on machines with bash. steps: -- bash: . '$(Build.SourcesDirectory)/ci/build.sh' +- bash: . '$(Build.SourcesDirectory)/azure-pipelines/build.sh' displayName: Build workingDirectory: '$(Build.BinariesDirectory)' env: ${{ parameters.environmentVariables }} -- bash: . '$(Build.SourcesDirectory)/ci/test.sh' +- bash: . '$(Build.SourcesDirectory)/azure-pipelines/test.sh' displayName: Test workingDirectory: '$(Build.BinariesDirectory)' env: ${{ parameters.environmentVariables }} diff --git a/ci/build.sh b/azure-pipelines/build.sh similarity index 64% rename from ci/build.sh rename to azure-pipelines/build.sh index 7ffa610ae..27e2f3e38 100755 --- a/ci/build.sh +++ b/azure-pipelines/build.sh @@ -9,7 +9,9 @@ set -e SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} BUILD_DIR=$(pwd) -CC=${CC:-cc} +BUILD_PATH=${BUILD_PATH:=$PATH} +CMAKE=$(which cmake) +CMAKE_GENERATOR=${CMAKE_GENERATOR:-Unix Makefiles} indent() { sed "s/^/ /"; } @@ -24,28 +26,31 @@ fi if [ -f "/etc/debian_version" ]; then echo "Debian version:" - lsb_release -a | indent + (source /etc/lsb-release && echo "${DISTRIB_DESCRIPTION}") | indent fi echo "Kernel version:" uname -a 2>&1 | indent echo "CMake version:" -cmake --version 2>&1 | indent -echo "Compiler version:" -$CC --version 2>&1 | indent +env PATH="$BUILD_PATH" "$CMAKE" --version 2>&1 | indent + +if test -n "$CC"; then + echo "Compiler version:" + "$CC" --version 2>&1 | indent +fi echo "" echo "##############################################################################" echo "## Configuring build environment" echo "##############################################################################" -echo cmake ${SOURCE_DIR} -DENABLE_WERROR=ON -DBUILD_EXAMPLES=ON -DBUILD_FUZZERS=ON -DUSE_STANDALONE_FUZZERS=ON ${CMAKE_OPTIONS} -cmake ${SOURCE_DIR} -DENABLE_WERROR=ON -DBUILD_EXAMPLES=ON -DBUILD_FUZZERS=ON -DUSE_STANDALONE_FUZZERS=ON ${CMAKE_OPTIONS} +echo cmake ${SOURCE_DIR} -DENABLE_WERROR=ON -DBUILD_EXAMPLES=ON -DBUILD_FUZZERS=ON -DUSE_STANDALONE_FUZZERS=ON -G \"${CMAKE_GENERATOR}\" ${CMAKE_OPTIONS} +env PATH="$BUILD_PATH" "$CMAKE" ${SOURCE_DIR} -DENABLE_WERROR=ON -DBUILD_EXAMPLES=ON -DBUILD_FUZZERS=ON -DUSE_STANDALONE_FUZZERS=ON -G "${CMAKE_GENERATOR}" ${CMAKE_OPTIONS} echo "" echo "##############################################################################" echo "## Building libgit2" echo "##############################################################################" -cmake --build . +env PATH="$BUILD_PATH" "$CMAKE" --build . diff --git a/azure-pipelines/coverity.sh b/azure-pipelines/coverity.sh new file mode 100755 index 000000000..c68b6f8cc --- /dev/null +++ b/azure-pipelines/coverity.sh @@ -0,0 +1,62 @@ +#!/bin/bash -e + +if test -z "$COVERITY_TOKEN" +then + echo "Need to set a coverity token" + exit 1 +fi + +case $(uname -m) in + i?86) + BITS=32;; + amd64|x86_64) + BITS=64;; + *) + echo "Unsupported arch '$(uname -m)'" + exit 1;; +esac + +SCAN_TOOL=https://scan.coverity.com/download/cxx/linux${BITS} +SOURCE_DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")"/..) +BUILD_DIR=${SOURCE_DIR}/coverity-build +TOOL_DIR=${BUILD_DIR}/coverity-tools + +# Install coverity tools +if ! test -d "$TOOL_DIR" +then + mkdir -p "$TOOL_DIR" + curl --silent --show-error --location --data "project=libgit2&token=$COVERITY_TOKEN" "$SCAN_TOOL" | + tar -xzC "$TOOL_DIR" + ln -s "$(find "$TOOL_DIR" -type d -name 'cov-analysis*')" "$TOOL_DIR"/cov-analysis +fi + +cp "${SOURCE_DIR}/script/user_nodefs.h" "$TOOL_DIR"/cov-analysis/config/ + +# Build libgit2 with Coverity +mkdir -p "$BUILD_DIR" +cd "$BUILD_DIR" +cmake "$SOURCE_DIR" +COVERITY_UNSUPPORTED=1 \ + "$TOOL_DIR/cov-analysis/bin/cov-build" --dir cov-int \ + cmake --build . + +# Upload results +tar -czf libgit2.tgz cov-int +REVISION=$(cd ${SOURCE_DIR} && git rev-parse --short HEAD) +HTML="$(curl \ + --silent --show-error \ + --write-out "\n%{http_code}" \ + --form token="$COVERITY_TOKEN" \ + --form email=libgit2@gmail.com \ + --form file=@libgit2.tgz \ + --form version="$REVISION" \ + --form description="libgit2 build" \ + https://scan.coverity.com/builds?project=libgit2)" + +# Status code is the last line +STATUS_CODE="$(echo "$HTML" | tail -n1)" +if test "${STATUS_CODE}" != 200 && test "${STATUS_CODE}" != 201 +then + echo "Received error code ${STATUS_CODE} from Coverity" + exit 1 +fi diff --git a/azure-pipelines/coverity.yml b/azure-pipelines/coverity.yml index d8d34229e..a8747db73 100644 --- a/azure-pipelines/coverity.yml +++ b/azure-pipelines/coverity.yml @@ -5,32 +5,22 @@ jobs: - job: coverity displayName: 'Coverity' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: + - script: | + cd $(Build.SourcesDirectory)/azure-pipelines/docker + docker build -t libgit2/xenial --build-arg BASE=ubuntu:xenial -f xenial . + displayName: 'Build Docker image' - task: Docker@0 - displayName: Build + displayName: Analyze inputs: action: 'Run an image' - imageName: 'libgit2/trusty-openssl:latest' + imageName: libgit2/xenial volumes: | - $(Build.SourcesDirectory):/src - $(Build.BinariesDirectory):/build + $(Build.SourcesDirectory):/home/libgit2/source + $(Build.BinariesDirectory):/home/libgit2/build envVars: | COVERITY_TOKEN=$(COVERITY_TOKEN) - workDir: '/build' - containerCommand: '/src/ci/coverity-build.sh' + workDir: '/home/libgit2/build' + containerCommand: '/home/libgit2/source/azure-pipelines/coverity.sh' detached: false - - task: Docker@0 - displayName: Publish - inputs: - action: 'Run an image' - imageName: 'libgit2/trusty-openssl:latest' - volumes: | - $(Build.SourcesDirectory):/src - $(Build.BinariesDirectory):/build - envVars: | - COVERITY_TOKEN=$(COVERITY_TOKEN) - workDir: '/build' - containerCommand: '/src/ci/coverity-publish.sh' - detached: false - continueOnError: true diff --git a/azure-pipelines/docker.yml b/azure-pipelines/docker.yml index 2744a63ae..0f0885770 100644 --- a/azure-pipelines/docker.yml +++ b/azure-pipelines/docker.yml @@ -4,29 +4,43 @@ steps: - script: docker run --rm --privileged multiarch/qemu-user-static:register --reset displayName: 'Register Docker QEMU' +- task: cache@2 + displayName: Cache Docker layers + inputs: + key: docker + path: /tmp/dockercache +- script: | + if [ -f /tmp/dockercache/${{parameters.docker.image}}.tar ]; then docker load < /tmp/dockercache/${{parameters.docker.image}}.tar; fi + displayName: 'Load Docker cache' +- script: | + cd $(Build.SourcesDirectory)/azure-pipelines/docker && + docker build -t libgit2/${{parameters.docker.image}} --build-arg BASE=${{parameters.docker.base}} -f ${{parameters.docker.image}} . && + if [ ! -d /tmp/dockercache ]; then mkdir /tmp/dockercache; fi && + docker save libgit2/${{parameters.docker.image}} $(docker history -q libgit2/${{parameters.docker.image}} | grep -v '') > /tmp/dockercache/${{parameters.docker.image}}.tar + displayName: 'Build Docker image' - task: docker@0 displayName: Build inputs: action: 'Run an image' - imageName: ${{ parameters.imageName }} + imageName: libgit2/${{ parameters.docker.image }} volumes: | - $(Build.SourcesDirectory):/src - $(Build.BinariesDirectory):/build + $(Build.SourcesDirectory):/home/libgit2/source + $(Build.BinariesDirectory):/home/libgit2/build envVars: ${{ parameters.environmentVariables }} - workDir: '/build' - containerCommand: '/src/ci/build.sh' + workDir: '/home/libgit2/build' + containerCommand: '/home/libgit2/source/azure-pipelines/build.sh' detached: false - task: docker@0 displayName: Test inputs: action: 'Run an image' - imageName: ${{ parameters.imageName }} + imageName: libgit2/${{ parameters.docker.image }} volumes: | - $(Build.SourcesDirectory):/src - $(Build.BinariesDirectory):/build + $(Build.SourcesDirectory):/home/libgit2/source + $(Build.BinariesDirectory):/home/libgit2/build envVars: ${{ parameters.environmentVariables }} - workDir: '/build' - containerCommand: '/src/ci/test.sh' + workDir: '/home/libgit2/build' + containerCommand: '/home/libgit2/source/azure-pipelines/test.sh' detached: false - task: publishtestresults@2 displayName: Publish Test Results diff --git a/azure-pipelines/docker/bionic b/azure-pipelines/docker/bionic new file mode 100644 index 000000000..65a14063a --- /dev/null +++ b/azure-pipelines/docker/bionic @@ -0,0 +1,42 @@ +ARG BASE +FROM $BASE AS apt +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + clang \ + cmake \ + curl \ + gcc \ + git \ + libcurl4-openssl-dev \ + libpcre3-dev \ + libssh2-1-dev \ + libssl-dev \ + libz-dev \ + ninja-build \ + openjdk-8-jre-headless \ + openssh-server \ + openssl \ + pkgconf \ + python \ + sudo \ + valgrind \ + && \ + rm -rf /var/lib/apt/lists/* + +FROM apt AS mbedtls +RUN cd /tmp && \ + curl --location --silent --show-error https://tls.mbed.org/download/mbedtls-2.16.2-apache.tgz | \ + tar -xz && \ + cd mbedtls-2.16.2 && \ + scripts/config.pl set MBEDTLS_MD4_C 1 && \ + CFLAGS=-fPIC cmake -G Ninja -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF -DUSE_SHARED_MBEDTLS_LIBRARY=OFF -DUSE_STATIC_MBEDTLS_LIBRARY=ON . && \ + ninja install && \ + cd .. && \ + rm -rf mbedtls-2.16.2 + +FROM mbedtls AS configure +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod a+x /usr/local/bin/entrypoint.sh +RUN mkdir /var/run/sshd + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/azure-pipelines/docker/docurium b/azure-pipelines/docker/docurium new file mode 100644 index 000000000..54a4202b6 --- /dev/null +++ b/azure-pipelines/docker/docurium @@ -0,0 +1,3 @@ +FROM ubuntu:bionic +RUN apt update && apt install -y cmake pkg-config ruby ruby-dev llvm libclang-dev libssl-dev python-pygments +RUN gem install docurium diff --git a/azure-pipelines/docker/entrypoint.sh b/azure-pipelines/docker/entrypoint.sh new file mode 100644 index 000000000..8d96e3acd --- /dev/null +++ b/azure-pipelines/docker/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/bash -e +useradd --shell /bin/bash libgit2 +chown --recursive libgit2:libgit2 /home/libgit2 +exec sudo --preserve-env --set-home --user=libgit2 "$@" diff --git a/azure-pipelines/docker/xenial b/azure-pipelines/docker/xenial new file mode 100644 index 000000000..bfb96d9db --- /dev/null +++ b/azure-pipelines/docker/xenial @@ -0,0 +1,67 @@ +ARG BASE +FROM $BASE AS apt +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + bzip2 \ + clang \ + cmake \ + curl \ + gcc \ + git \ + krb5-user \ + libcurl4-gnutls-dev \ + libgcrypt20-dev \ + libkrb5-dev \ + libpcre3-dev \ + libssl-dev \ + libz-dev \ + make \ + ninja-build \ + openjdk-8-jre-headless \ + openssh-server \ + openssl \ + pkgconf \ + python \ + sudo \ + valgrind \ + && \ + rm -rf /var/lib/apt/lists/* + +FROM apt AS mbedtls +RUN cd /tmp && \ + curl --location --silent --show-error https://tls.mbed.org/download/mbedtls-2.16.2-apache.tgz | \ + tar -xz && \ + cd mbedtls-2.16.2 && \ + scripts/config.pl set MBEDTLS_MD4_C 1 && \ + CFLAGS=-fPIC cmake -G Ninja -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF -DUSE_SHARED_MBEDTLS_LIBRARY=OFF -DUSE_STATIC_MBEDTLS_LIBRARY=ON . && \ + ninja install && \ + cd .. && \ + rm -rf mbedtls-2.16.2 + +FROM mbedtls AS libssh2 +RUN cd /tmp && \ + curl --insecure --location --silent --show-error https://www.libssh2.org/download/libssh2-1.8.2.tar.gz | \ + tar -xz && \ + cd libssh2-1.8.2 && \ + CFLAGS=-fPIC cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCRYPTO_BACKEND=Libgcrypt . && \ + ninja install && \ + cd .. && \ + rm -rf libssh2-1.8.2 + +FROM libssh2 AS valgrind +RUN cd /tmp && \ + curl --insecure --location --silent --show-error https://sourceware.org/pub/valgrind/valgrind-3.15.0.tar.bz2 | \ + tar -xj && \ + cd valgrind-3.15.0 && \ + ./configure && \ + make && \ + make install && \ + cd .. && \ + rm -rf valgrind-3.15.0 + +FROM valgrind AS configure +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod a+x /usr/local/bin/entrypoint.sh +RUN mkdir /var/run/sshd + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/azure-pipelines/nightly.yml b/azure-pipelines/nightly.yml index fd76b97a8..a75a9cc24 100644 --- a/azure-pipelines/nightly.yml +++ b/azure-pipelines/nightly.yml @@ -2,173 +2,200 @@ resources: - repo: self jobs: -- job: linux_amd64_trusty_gcc_openssl - displayName: 'Linux (amd64; Trusty; GCC; OpenSSL)' +- job: linux_amd64_xenial_gcc_openssl + displayName: 'Linux (amd64; Xenial; GCC; OpenSSL)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: docker.yml parameters: - imageName: 'libgit2/trusty-amd64:latest' + docker: + image: xenial + base: ubuntu:xenial environmentVariables: | CC=gcc - CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON - LEAK_CHECK=valgrind + CMAKE_GENERATOR=Ninja + CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DREGEX_BACKEND=builtin -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind RUN_INVASIVE_TESTS=true -- job: linux_amd64_trusty_gcc_mbedtls - displayName: 'Linux (amd64; Trusty; GCC; mbedTLS)' +- job: linux_amd64_xenial_gcc_mbedtls + displayName: 'Linux (amd64; Xenial; GCC; mbedTLS)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: docker.yml parameters: - imageName: 'libgit2/trusty-amd64:latest' + docker: + image: xenial + base: ubuntu:xenial environmentVariables: | CC=gcc - CMAKE_OPTIONS=-DUSE_HTTPS=mbedTLS -DSHA1_BACKEND=mbedTLS -DDEPRECATE_HARD=ON - LEAK_CHECK=valgrind + CMAKE_GENERATOR=Ninja + CMAKE_OPTIONS=-DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind RUN_INVASIVE_TESTS=true -- job: linux_amd64_trusty_clang_openssl - displayName: 'Linux (amd64; Trusty; Clang; OpenSSL)' +- job: linux_amd64_xenial_clang_openssl + displayName: 'Linux (amd64; Xenial; Clang; OpenSSL)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: docker.yml parameters: - imageName: 'libgit2/trusty-amd64:latest' + docker: + image: xenial + base: ubuntu:xenial environmentVariables: | CC=clang - CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON - LEAK_CHECK=valgrind + CMAKE_GENERATOR=Ninja + CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind RUN_INVASIVE_TESTS=true -- job: linux_amd64_trusty_clang_mbedtls - displayName: 'Linux (amd64; Trusty; Clang; mbedTLS)' +- job: linux_amd64_xenial_clang_mbedtls + displayName: 'Linux (amd64; Xenial; Clang; mbedTLS)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: docker.yml parameters: - imageName: 'libgit2/trusty-amd64:latest' + docker: + image: xenial + base: ubuntu:xenial environmentVariables: | CC=clang - CMAKE_OPTIONS=-DUSE_HTTPS=mbedTLS -DSHA1_BACKEND=mbedTLS -DDEPRECATE_HARD=ON - LEAK_CHECK=valgrind + CMAKE_GENERATOR=Ninja + CMAKE_OPTIONS=-DUSE_HTTPS=mbedTLS -DUSE_SHA1=HTTPS -DREGEX_BACKEND=pcre -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind RUN_INVASIVE_TESTS=true - job: macos - displayName: 'macOS' + displayName: 'macOS (amd64; 10.15)' pool: - vmImage: 'macOS 10.13' + vmImage: 'macOS-10.15' steps: - - bash: . '$(Build.SourcesDirectory)/ci/setup-osx.sh' + - bash: . '$(Build.SourcesDirectory)/azure-pipelines/setup-osx.sh' displayName: Setup - template: bash.yml parameters: environmentVariables: TMPDIR: $(Agent.TempDirectory) PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig - LEAK_CHECK: leaks - CMAKE_OPTIONS: -G Ninja -DDEPRECATE_HARD=ON + CMAKE_GENERATOR: Ninja + CMAKE_OPTIONS: -DREGEX_BACKEND=regcomp_l -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=leaks -DUSE_GSSAPI=ON RUN_INVASIVE_TESTS: true SKIP_SSH_TESTS: true - job: windows_vs_amd64 displayName: 'Windows (amd64; Visual Studio)' - pool: Hosted + pool: + vmImage: 'vs2017-win2016' steps: - - template: powershell.yml + - template: bash.yml parameters: environmentVariables: - CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013 Win64" -DDEPRECATE_HARD=ON + CMAKE_GENERATOR: Visual Studio 15 2017 + CMAKE_OPTIONS: -A x64 -DMSVC_CRTDBG=ON -DDEPRECATE_HARD=ON RUN_INVASIVE_TESTS: true + SKIP_SSH_TESTS: true - job: windows_vs_x86 displayName: 'Windows (x86; Visual Studio)' - pool: Hosted + pool: + vmImage: 'vs2017-win2016' steps: - - template: powershell.yml + - template: bash.yml parameters: environmentVariables: - CMAKE_OPTIONS: -DMSVC_CRTDBG=ON -G"Visual Studio 12 2013" -DDEPRECATE_HARD=ON + CMAKE_GENERATOR: Visual Studio 15 2017 + CMAKE_OPTIONS: -A Win32 -DMSVC_CRTDBG=ON -DDEPRECATE_HARD=ON -DUSE_SHA1=HTTPS RUN_INVASIVE_TESTS: true + SKIP_SSH_TESTS: true - job: windows_mingw_amd64 displayName: 'Windows (amd64; MinGW)' - pool: Hosted + pool: + vmImage: 'vs2017-win2016' steps: - - powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1' + - bash: . '$(Build.SourcesDirectory)\azure-pipelines\setup-mingw.sh' displayName: Setup env: TEMP: $(Agent.TempDirectory) ARCH: amd64 - - template: powershell.yml + - template: bash.yml parameters: environmentVariables: - CMAKE_OPTIONS: -G"MinGW Makefiles" -DDEPRECATE_HARD=ON - PATH: $(Agent.TempDirectory)\mingw64\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin + BUILD_PATH: $(Agent.TempDirectory)\mingw64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin + CMAKE_GENERATOR: MinGW Makefiles + CMAKE_OPTIONS: -DDEPRECATE_HARD=ON RUN_INVASIVE_TESTS: true + SKIP_SSH_TESTS: true - job: windows_mingw_x86 displayName: 'Windows (x86; MinGW)' - pool: Hosted + pool: + vmImage: 'vs2017-win2016' steps: - - powershell: . '$(Build.SourcesDirectory)\ci\setup-mingw.ps1' + - bash: . '$(Build.SourcesDirectory)\azure-pipelines\setup-mingw.sh' displayName: Setup workingDirectory: '$(Build.BinariesDirectory)' env: TEMP: $(Agent.TempDirectory) ARCH: x86 - - template: powershell.yml + - template: bash.yml parameters: environmentVariables: - CMAKE_OPTIONS: -G"MinGW Makefiles" -DDEPRECATE_HARD=ON - PATH: $(Agent.TempDirectory)\mingw32\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\CMake\bin + BUILD_PATH: $(Agent.TempDirectory)\mingw32\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin + CMAKE_GENERATOR: MinGW Makefiles + CMAKE_OPTIONS: -DDEPRECATE_HARD=ON RUN_INVASIVE_TESTS: true + SKIP_SSH_TESTS: true - job: linux_x86_bionic_gcc_openssl displayName: 'Linux (x86; Bionic; GCC; OpenSSL)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: docker.yml parameters: qemu: 'true' - imageName: 'libgit2/bionic-x86:latest' + docker: + image: bionic + base: multiarch/ubuntu-core:x86-bionic environmentVariables: | CC=gcc - CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON - LEAK_CHECK=valgrind + CMAKE_GENERATOR=Ninja + CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind RUN_INVASIVE_TESTS=true - job: linux_x86_bionic_clang_openssl displayName: 'Linux (x86; Bionic; Clang; OpenSSL)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: docker.yml parameters: qemu: 'true' - imageName: 'libgit2/bionic-x86:latest' + docker: + image: bionic + base: multiarch/ubuntu-core:x86-bionic environmentVariables: | CC=clang - CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON - LEAK_CHECK=valgrind + CMAKE_GENERATOR=Ninja + CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON -DUSE_LEAK_CHECKER=valgrind RUN_INVASIVE_TESTS=true - job: linux_arm32_bionic_gcc_openssl displayName: 'Linux (arm32; Bionic; GCC; OpenSSL)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: docker.yml parameters: qemu: 'true' - imageName: 'libgit2/bionic-arm32:latest' + docker: + image: bionic + base: multiarch/ubuntu-core:armhf-bionic environmentVariables: | CC=gcc + CMAKE_GENERATOR=Ninja CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON RUN_INVASIVE_TESTS=true SKIP_PROXY_TESTS=true @@ -176,14 +203,17 @@ jobs: - job: linux_arm64_bionic_gcc_openssl displayName: 'Linux (arm64; Bionic; GCC; OpenSSL)' pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-18.04' steps: - template: docker.yml parameters: qemu: 'true' - imageName: 'libgit2/bionic-arm64:latest' + docker: + image: bionic + base: multiarch/ubuntu-core:arm64-bionic environmentVariables: | CC=gcc + CMAKE_GENERATOR=Ninja CMAKE_OPTIONS=-DUSE_HTTPS=OpenSSL -DDEPRECATE_HARD=ON RUN_INVASIVE_TESTS=true SKIP_PROXY_TESTS=true diff --git a/azure-pipelines/powershell.yml b/azure-pipelines/powershell.yml deleted file mode 100644 index a2eb175d5..000000000 --- a/azure-pipelines/powershell.yml +++ /dev/null @@ -1,17 +0,0 @@ -# These are the steps used for building on machines with PowerShell. -steps: -- powershell: . '$(Build.SourcesDirectory)\ci\build.ps1' - displayName: Build - workingDirectory: '$(Build.BinariesDirectory)' - env: ${{ parameters.environmentVariables }} -- powershell: . '$(Build.SourcesDirectory)\ci\test.ps1' - displayName: Test - workingDirectory: '$(Build.BinariesDirectory)' - env: ${{ parameters.environmentVariables }} -- task: PublishTestResults@2 - displayName: Publish Test Results - condition: succeededOrFailed() - inputs: - testResultsFiles: 'results_*.xml' - searchFolder: '$(Build.BinariesDirectory)' - mergeTestResults: true diff --git a/azure-pipelines/setup-mingw.sh b/azure-pipelines/setup-mingw.sh new file mode 100755 index 000000000..1172c2077 --- /dev/null +++ b/azure-pipelines/setup-mingw.sh @@ -0,0 +1,15 @@ +#!/bin/sh -e + +echo "##############################################################################" +echo "## Downloading mingw" +echo "##############################################################################" + +case "$ARCH" in + amd64) + MINGW_URI="https://bintray.com/libgit2/build-dependencies/download_file?file_path=mingw-w64-x86_64-8.1.0-release-win32-seh-rt_v6-rev0.zip";; + x86) + MINGW_URI="https://bintray.com/libgit2/build-dependencies/download_file?file_path=mingw-w64-i686-8.1.0-release-win32-sjlj-rt_v6-rev0.zip";; +esac + +curl -s -L "$MINGW_URI" -o "$TEMP"/mingw-"$ARCH".zip +unzip -q "$TEMP"/mingw-"$ARCH".zip -d "$TEMP" diff --git a/ci/setup-osx.sh b/azure-pipelines/setup-osx.sh similarity index 100% rename from ci/setup-osx.sh rename to azure-pipelines/setup-osx.sh diff --git a/ci/test.sh b/azure-pipelines/test.sh similarity index 58% rename from ci/test.sh rename to azure-pipelines/test.sh index 136ff2581..2b43ba198 100755 --- a/ci/test.sh +++ b/azure-pipelines/test.sh @@ -6,6 +6,11 @@ if [ -n "$SKIP_TESTS" ]; then exit 0 fi +# Windows doesn't run the NTLM tests properly (yet) +if [[ "$(uname -s)" == MINGW* ]]; then + SKIP_NTLM_TESTS=1 +fi + SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} BUILD_DIR=$(pwd) TMPDIR=${TMPDIR:-/tmp} @@ -13,15 +18,12 @@ USER=${USER:-$(whoami)} SUCCESS=1 -VALGRIND="valgrind --leak-check=full --show-reachable=yes --error-exitcode=125 --num-callers=50 --suppressions=\"$SOURCE_DIR/libgit2_clar.supp\"" -LEAKS="MallocStackLogging=1 MallocScribble=1 MallocLogFile=/dev/null CLAR_AT_EXIT=\"leaks -quiet \$PPID\"" - cleanup() { echo "Cleaning up..." - if [ ! -z "$GITDAEMON_DIR" -a -f "${GITDAEMON_DIR}/pid" ]; then + if [ ! -z "$GITDAEMON_PID" ]; then echo "Stopping git daemon..." - kill $(cat "${GITDAEMON_DIR}/pid") + kill $GITDAEMON_PID fi if [ ! -z "$SSHD_DIR" -a -f "${SSHD_DIR}/pid" ]; then @@ -32,35 +34,38 @@ cleanup() { echo "Done." } -failure() { - echo "Test exited with code: $1" - SUCCESS=0 -} - -# Ask ctest what it would run if we were to invoke it directly. This lets -# us manage the test configuration in a single place (tests/CMakeLists.txt) -# instead of running clar here as well. But it allows us to wrap our test -# harness with a leak checker like valgrind. Append the option to write -# JUnit-style XML files. run_test() { - TEST_CMD=$(ctest -N -V -R "^${1}$" | sed -n 's/^[0-9]*: Test command: //p') - - if [ -z "$TEST_CMD" ]; then - echo "Could not find tests: $1" - exit 1 - fi - - TEST_CMD="${TEST_CMD} -r${BUILD_DIR}/results_${1}.xml" - - if [ "$LEAK_CHECK" = "valgrind" ]; then - RUNNER="$VALGRIND $TEST_CMD" - elif [ "$LEAK_CHECK" = "leaks" ]; then - RUNNER="$LEAKS $TEST_CMD" + if [[ "$GITTEST_FLAKY_RETRY" > 0 ]]; then + ATTEMPTS_REMAIN=$GITTEST_FLAKY_RETRY else - RUNNER="$TEST_CMD" + ATTEMPTS_REMAIN=1 fi - eval $RUNNER || failure + FAILED=0 + while [[ "$ATTEMPTS_REMAIN" > 0 ]]; do + if [ "$FAILED" -eq 1 ]; then + echo "" + echo "Re-running flaky ${1} tests..." + echo "" + fi + + RETURN_CODE=0 + + CLAR_SUMMARY="${BUILD_DIR}/results_${1}.xml" ctest -V -R "^${1}$" || RETURN_CODE=$? && true + + if [ "$RETURN_CODE" -eq 0 ]; then + FAILED=0 + break + fi + + echo "Test exited with code: $RETURN_CODE" + ATTEMPTS_REMAIN="$(($ATTEMPTS_REMAIN-1))" + FAILED=1 + done + + if [ "$FAILED" -ne 0 ]; then + SUCCESS=0 + fi } # Configure the test environment; run them early so that we're certain @@ -74,13 +79,31 @@ if [ -z "$SKIP_GITDAEMON_TESTS" ]; then echo "Starting git daemon..." GITDAEMON_DIR=`mktemp -d ${TMPDIR}/gitdaemon.XXXXXXXX` git init --bare "${GITDAEMON_DIR}/test.git" - git daemon --listen=localhost --export-all --enable=receive-pack --pid-file="${GITDAEMON_DIR}/pid" --base-path="${GITDAEMON_DIR}" "${GITDAEMON_DIR}" 2>/dev/null & + git daemon --listen=localhost --export-all --enable=receive-pack --base-path="${GITDAEMON_DIR}" "${GITDAEMON_DIR}" 2>/dev/null & + GITDAEMON_PID=$! + disown $GITDAEMON_PID fi if [ -z "$SKIP_PROXY_TESTS" ]; then - echo "Starting HTTP proxy..." - curl -L https://github.com/ethomson/poxyproxy/releases/download/v0.4.0/poxyproxy-0.4.0.jar >poxyproxy.jar - java -jar poxyproxy.jar -d --address 127.0.0.1 --port 8080 --credentials foo:bar --quiet & + curl --location --silent --show-error https://github.com/ethomson/poxyproxy/releases/download/v0.7.0/poxyproxy-0.7.0.jar >poxyproxy.jar + + echo "" + echo "Starting HTTP proxy (Basic)..." + java -jar poxyproxy.jar --address 127.0.0.1 --port 8080 --credentials foo:bar --auth-type basic --quiet & + + echo "" + echo "Starting HTTP proxy (NTLM)..." + java -jar poxyproxy.jar --address 127.0.0.1 --port 8090 --credentials foo:bar --auth-type ntlm --quiet & +fi + +if [ -z "$SKIP_NTLM_TESTS" ]; then + curl --location --silent --show-error https://github.com/ethomson/poxygit/releases/download/v0.4.0/poxygit-0.4.0.jar >poxygit.jar + + echo "" + echo "Starting HTTP server..." + NTLM_DIR=`mktemp -d ${TMPDIR}/ntlm.XXXXXXXX` + git init --bare "${NTLM_DIR}/test.git" + java -jar poxygit.jar --address 127.0.0.1 --port 9000 --credentials foo:baz --quiet "${NTLM_DIR}" & fi if [ -z "$SKIP_SSH_TESTS" ]; then @@ -160,7 +183,9 @@ if [ -z "$SKIP_ONLINE_TESTS" ]; then echo "## Running (online) tests" echo "##############################################################################" + export GITTEST_FLAKY_RETRY=5 run_test online + unset GITTEST_FLAKY_RETRY fi if [ -z "$SKIP_GITDAEMON_TESTS" ]; then @@ -175,7 +200,7 @@ fi if [ -z "$SKIP_PROXY_TESTS" ]; then echo "" - echo "Running proxy tests" + echo "Running proxy tests (Basic authentication)" echo "" export GITTEST_REMOTE_PROXY_HOST="localhost:8080" @@ -185,6 +210,79 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then unset GITTEST_REMOTE_PROXY_HOST unset GITTEST_REMOTE_PROXY_USER unset GITTEST_REMOTE_PROXY_PASS + + echo "" + echo "Running proxy tests (NTLM authentication)" + echo "" + + export GITTEST_REMOTE_PROXY_HOST="localhost:8090" + export GITTEST_REMOTE_PROXY_USER="foo" + export GITTEST_REMOTE_PROXY_PASS="bar" + export GITTEST_FLAKY_RETRY=5 + run_test proxy + unset GITTEST_FLAKY_RETRY + unset GITTEST_REMOTE_PROXY_HOST + unset GITTEST_REMOTE_PROXY_USER + unset GITTEST_REMOTE_PROXY_PASS +fi + +if [ -z "$SKIP_NTLM_TESTS" ]; then + echo "" + echo "Running NTLM tests (IIS emulation)" + echo "" + + export GITTEST_REMOTE_URL="http://localhost:9000/ntlm/test.git" + export GITTEST_REMOTE_USER="foo" + export GITTEST_REMOTE_PASS="baz" + run_test auth_clone_and_push + unset GITTEST_REMOTE_URL + unset GITTEST_REMOTE_USER + unset GITTEST_REMOTE_PASS + + echo "" + echo "Running NTLM tests (Apache emulation)" + echo "" + + export GITTEST_REMOTE_URL="http://localhost:9000/broken-ntlm/test.git" + export GITTEST_REMOTE_USER="foo" + export GITTEST_REMOTE_PASS="baz" + run_test auth_clone_and_push + unset GITTEST_REMOTE_URL + unset GITTEST_REMOTE_USER + unset GITTEST_REMOTE_PASS +fi + +if [ -z "$SKIP_NEGOTIATE_TESTS" -a -n "$GITTEST_NEGOTIATE_PASSWORD" ]; then + echo "" + echo "Running SPNEGO tests" + echo "" + + if [ "$(uname -s)" = "Darwin" ]; then + KINIT_FLAGS="--password-file=STDIN" + fi + + echo $GITTEST_NEGOTIATE_PASSWORD | kinit $KINIT_FLAGS test@LIBGIT2.ORG + klist -5f + + export GITTEST_REMOTE_URL="https://test.libgit2.org/kerberos/empty.git" + export GITTEST_REMOTE_DEFAULT="true" + run_test auth_clone + unset GITTEST_REMOTE_URL + unset GITTEST_REMOTE_DEFAULT + + echo "" + echo "Running SPNEGO tests (expect/continue)" + echo "" + + export GITTEST_REMOTE_URL="https://test.libgit2.org/kerberos/empty.git" + export GITTEST_REMOTE_DEFAULT="true" + export GITTEST_REMOTE_EXPECTCONTINUE="true" + run_test auth_clone + unset GITTEST_REMOTE_URL + unset GITTEST_REMOTE_DEFAULT + unset GITTEST_REMOTE_EXPECTCONTINUE + + kdestroy -A fi if [ -z "$SKIP_SSH_TESTS" ]; then @@ -213,14 +311,12 @@ if [ -z "$SKIP_FUZZERS" ]; then echo "## Running fuzzers" echo "##############################################################################" - for fuzzer in fuzzers/*_fuzzer; do - "${fuzzer}" "${SOURCE_DIR}/fuzzers/corpora/$(basename "${fuzzer%_fuzzer}")" || failure - done + ctest -V -R 'fuzzer' fi cleanup -if [ "$SUCCESS" -ne "1" ]; then +if [ "$SUCCESS" -ne 1 ]; then echo "Some tests failed." exit 1 fi diff --git a/ci/build.ps1 b/ci/build.ps1 deleted file mode 100644 index 159c1dd1b..000000000 --- a/ci/build.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -Set-StrictMode -Version Latest - -$ErrorActionPreference = "Stop" -$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' - -if ($Env:SOURCE_DIR) { $SourceDirectory = $Env:SOURCE_DIR } else { $SourceDirectory = Split-Path (Split-Path $MyInvocation.MyCommand.Path -Parent) -Parent } -$BuildDirectory = $(Get-Location).Path - -Write-Host "Source directory: ${SourceDirectory}" -Write-Host "Build directory: ${BuildDirectory}" -Write-Host "" -Write-Host "Operating system version:" -Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, ServicePackMajorVersion, BuildNumber, OSArchitecture | Format-List -Write-Host "PATH: ${Env:PATH}" -Write-Host "" - -Write-Host "##############################################################################" -Write-Host "## Configuring build environment" -Write-Host "##############################################################################" - -Invoke-Expression "cmake ${SourceDirectory} -DBUILD_EXAMPLES=ON ${Env:CMAKE_OPTIONS}" -if ($LastExitCode -ne 0) { [Environment]::Exit($LastExitCode) } - -Write-Host "" -Write-Host "##############################################################################" -Write-Host "## Building libgit2" -Write-Host "##############################################################################" - -cmake --build . -if ($LastExitCode -ne 0) { [Environment]::Exit($LastExitCode) } diff --git a/ci/coverity-build.sh b/ci/coverity-build.sh deleted file mode 100755 index f8264fa83..000000000 --- a/ci/coverity-build.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -set -e - -# Environment check -[ -z "$COVERITY_TOKEN" ] && echo "Need to set a coverity token" && exit 1 - -SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} -BUILD_DIR=$(pwd) - -case $(uname -m) in - i?86) BITS=32 ;; - amd64|x86_64) BITS=64 ;; -esac -SCAN_TOOL=https://scan.coverity.com/download/cxx/linux${BITS} -TOOL_BASE=$(pwd)/_coverity-scan - -# Install coverity tools -if [ ! -d "$TOOL_BASE" ]; then - echo "Downloading coverity..." - mkdir -p "$TOOL_BASE" - pushd "$TOOL_BASE" - wget -O coverity_tool.tgz $SCAN_TOOL \ - --post-data "project=libgit2&token=$COVERITY_TOKEN" - tar xzf coverity_tool.tgz - popd - TOOL_DIR=$(find "$TOOL_BASE" -type d -name 'cov-analysis*') - ln -s "$TOOL_DIR" "$TOOL_BASE"/cov-analysis -fi - -cp "${SOURCE_DIR}/script/user_nodefs.h" "$TOOL_BASE"/cov-analysis/config/user_nodefs.h - -COV_BUILD="$TOOL_BASE/cov-analysis/bin/cov-build" - -# Configure and build -cmake ${SOURCE_DIR} - -COVERITY_UNSUPPORTED=1 \ - $COV_BUILD --dir cov-int \ - cmake --build . - diff --git a/ci/coverity-publish.sh b/ci/coverity-publish.sh deleted file mode 100755 index 2341b13fb..000000000 --- a/ci/coverity-publish.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -set -e - -# Results check -[ ! -d "cov-int" ] && echo "Coverity directory not found" && exit 1 - -# Upload results -tar czf libgit2.tgz cov-int - -SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )} -SHA=$(cd ${SOURCE_DIR} && git rev-parse --short HEAD) - -HTML="$(curl \ - --silent \ - --write-out "\n%{http_code}" \ - --form token="$COVERITY_TOKEN" \ - --form email=libgit2@gmail.com \ - --form file=@libgit2.tgz \ - --form version="$SHA" \ - --form description="libgit2 build" \ - https://scan.coverity.com/builds?project=libgit2)" - -# Body is everything up to the last line -BODY="$(echo "$HTML" | head -n-1)" - -# Status code is the last line -STATUS_CODE="$(echo "$HTML" | tail -n1)" - -if [ "${STATUS_CODE}" != "200" -a "${STATUS_CODE}" != "201" ]; then - echo "Received error code ${STATUS_CODE} from Coverity" - exit 1 -fi diff --git a/ci/setup-linux.sh b/ci/setup-linux.sh deleted file mode 100755 index c5ecb550b..000000000 --- a/ci/setup-linux.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -set -e -set -x - -TMPDIR=${TMPDIR:-/tmp} - -if [ -z "$SKIP_APT" ]; then - apt-get update - apt-get -y install build-essential pkg-config clang cmake openssl libssl-dev libssh2-1-dev libcurl4-gnutls-dev openssh-server -fi - -mkdir -p /var/run/sshd - -if [ "$MBEDTLS" ]; then - MBEDTLS_DIR=${MBEDTLS_DIR:-$(mktemp -d ${TMPDIR}/mbedtls.XXXXXXXX)} - - git clone --depth 10 --single-branch --branch mbedtls-2.6.1 https://github.com/ARMmbed/mbedtls.git ${MBEDTLS_DIR} - cd ${MBEDTLS_DIR} - - CFLAGS=-fPIC cmake -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF -DUSE_SHARED_MBEDTLS_LIBRARY=OFF -DUSE_STATIC_MBEDTLS_LIBRARY=ON . - cmake --build . - - if [ -z "$SKIP_MBEDTLS_INSTALL" ]; then - make install - fi -fi diff --git a/ci/setup-mingw.ps1 b/ci/setup-mingw.ps1 deleted file mode 100644 index 76ecd3987..000000000 --- a/ci/setup-mingw.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -Set-StrictMode -Version Latest - -$ErrorActionPreference = "Stop" -$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' - -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - -[Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem"); - -Write-Host "##############################################################################" -Write-Host "## Downloading mingw" -Write-Host "##############################################################################" - -if ($env:ARCH -eq "amd64") { - $mingw_uri = "https://bintray.com/libgit2/build-dependencies/download_file?file_path=mingw-w64-x86_64-8.1.0-release-win32-seh-rt_v6-rev0.zip" - $platform = "x86_64" -} else { - $mingw_uri = "https://bintray.com/libgit2/build-dependencies/download_file?file_path=mingw-w64-i686-8.1.0-release-win32-sjlj-rt_v6-rev0.zip" - $platform = "x86" -} - -$wc = New-Object net.webclient -$wc.Downloadfile($mingw_uri, "${Env:TEMP}/mingw-${Env:ARCH}.zip") - -[System.IO.Compression.ZipFile]::ExtractToDirectory("${Env:TEMP}/mingw-${Env:ARCH}.zip", $Env:TEMP) diff --git a/ci/test.ps1 b/ci/test.ps1 deleted file mode 100644 index 06e0ab228..000000000 --- a/ci/test.ps1 +++ /dev/null @@ -1,96 +0,0 @@ -Set-StrictMode -Version Latest - -$ErrorActionPreference = "Stop" -$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' - -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - -$SourceDir = Split-Path (Split-Path (Get-Variable MyInvocation).Value.MyCommand.Path) -$BuildDir = Get-Location -$global:Success = $true - -if ($Env:SKIP_TESTS) { exit } - -# Ask ctest what it would run if we were to invoke it directly. This lets -# us manage the test configuration in a single place (tests/CMakeLists.txt) -# instead of running clar here as well. But it allows us to wrap our test -# harness with a leak checker like valgrind. Append the option to write -# JUnit-style XML files. -function run_test { - $TestName = $args[0] - - $TestCommand = (ctest -N -V -R "^$TestName$") -join "`n" - - if (-Not ($TestCommand -match "(?ms).*\n^[0-9]*: Test command: ")) { - echo "Could not find tests: $TestName" - exit - } - - $TestCommand = (ctest -N -V -R "^$TestName$") -join "`n" -replace "(?ms).*\n^[0-9]*: Test command: ","" -replace "\n.*","" - $TestCommand += " -r${BuildDir}\results_${TestName}.xml" - - Invoke-Expression $TestCommand - if ($LastExitCode -ne 0) { $global:Success = $false } -} - -Write-Host "##############################################################################" -Write-Host "## Configuring test environment" -Write-Host "##############################################################################" - -if (-not $Env:SKIP_PROXY_TESTS) { - Write-Host "" - Write-Host "Starting HTTP proxy..." - Invoke-WebRequest -Method GET -Uri https://github.com/ethomson/poxyproxy/releases/download/v0.4.0/poxyproxy-0.4.0.jar -OutFile poxyproxy.jar - javaw -jar poxyproxy.jar -d --port 8080 --credentials foo:bar --quiet -} - -Write-Host "" -Write-Host "##############################################################################" -Write-Host "## Running (offline) tests" -Write-Host "##############################################################################" - -run_test offline - -if ($Env:RUN_INVASIVE_TESTS) { - Write-Host "" - Write-Host "##############################################################################" - Write-Host "## Running (invasive) tests" - Write-Host "##############################################################################" - - $Env:GITTEST_INVASIVE_FS_SIZE=1 - $Env:GITTEST_INVASIVE_MEMORY=1 - $Env:GITTEST_INVASIVE_SPEED=1 - run_test invasive - $Env:GITTEST_INVASIVE_FS_SIZE=$null - $Env:GITTEST_INVASIVE_MEMORY=$null - $Env:GITTEST_INVASIVE_SPEED=$null -} - -if (-not $Env:SKIP_ONLINE_TESTS) { - Write-Host "" - Write-Host "##############################################################################" - Write-Host "## Running (online) tests" - Write-Host "##############################################################################" - - run_test online -} - -if (-not $Env:SKIP_PROXY_TESTS) { - Write-Host "" - Write-Host "Running proxy tests" - Write-Host "" - - $Env:GITTEST_REMOTE_PROXY_HOST="localhost:8080" - $Env:GITTEST_REMOTE_PROXY_USER="foo" - $Env:GITTEST_REMOTE_PROXY_PASS="bar" - - run_test proxy - - $Env:GITTEST_REMOTE_PROXY_HOST=$null - $Env:GITTEST_REMOTE_PROXY_USER=$null - $Env:GITTEST_REMOTE_PROXY_PASS=$null - - taskkill /F /IM javaw.exe -} - -if (-Not $global:Success) { exit 1 } diff --git a/cmake/Modules/EnableWarnings.cmake b/cmake/Modules/EnableWarnings.cmake index 72e1523c4..b61ed7e90 100644 --- a/cmake/Modules/EnableWarnings.cmake +++ b/cmake/Modules/EnableWarnings.cmake @@ -7,5 +7,9 @@ MACRO(DISABLE_WARNINGS flag) ENDMACRO() IF(ENABLE_WERROR) - ADD_C_FLAG_IF_SUPPORTED(-Werror) + IF(MSVC) + ADD_COMPILE_OPTIONS(-WX) + ELSE() + ADD_C_FLAG_IF_SUPPORTED(-Werror) + ENDIF() ENDIF() diff --git a/cmake/Modules/FindCoreFoundation.cmake b/cmake/Modules/FindCoreFoundation.cmake index e86ccbf03..191aa595c 100644 --- a/cmake/Modules/FindCoreFoundation.cmake +++ b/cmake/Modules/FindCoreFoundation.cmake @@ -10,14 +10,14 @@ FIND_PATH(COREFOUNDATION_INCLUDE_DIR NAMES CoreFoundation.h) FIND_LIBRARY(COREFOUNDATION_LIBRARIES NAMES CoreFoundation) IF (COREFOUNDATION_INCLUDE_DIR AND COREFOUNDATION_LIBRARIES) IF (NOT CoreFoundation_FIND_QUIETLY) - MESSAGE("-- Found CoreFoundation ${COREFOUNDATION_LIBRARIES}") + MESSAGE(STATUS "Found CoreFoundation ${COREFOUNDATION_LIBRARIES}") ENDIF() SET(COREFOUNDATION_FOUND TRUE) SET(COREFOUNDATION_LDFLAGS "-framework CoreFoundation") ENDIF () IF (CoreFoundation_FIND_REQUIRED AND NOT COREFOUNDATION_FOUND) - MESSAGE(FATAL "-- CoreFoundation not found") + MESSAGE(FATAL_ERROR "CoreFoundation not found") ENDIF() MARK_AS_ADVANCED( diff --git a/cmake/Modules/FindGSSAPI.cmake b/cmake/Modules/FindGSSAPI.cmake index 8520d35df..37357c4cd 100644 --- a/cmake/Modules/FindGSSAPI.cmake +++ b/cmake/Modules/FindGSSAPI.cmake @@ -6,7 +6,7 @@ # # Read-Only variables: # GSSAPI_FLAVOR_MIT - set to TURE if MIT Kerberos has been found -# GSSAPI_FLAVOR_HEIMDAL - set to TRUE if Heimdal Keberos has been found +# GSSAPI_FLAVOR_HEIMDAL - set to TRUE if Heimdal Kerberos has been found # GSSAPI_FOUND - system has GSSAPI # GSSAPI_INCLUDE_DIR - the GSSAPI include directory # GSSAPI_LIBRARIES - Link these to use GSSAPI diff --git a/cmake/Modules/FindGSSFramework.cmake b/cmake/Modules/FindGSSFramework.cmake new file mode 100644 index 000000000..dcf724916 --- /dev/null +++ b/cmake/Modules/FindGSSFramework.cmake @@ -0,0 +1,28 @@ +# Find GSS.framework +# This will define : +# +# GSSFRAMEWORK_FOUND +# GSSFRAMEWORK_INCLUDE_DIR +# GSSFRAMEWORK_LIBRARIES +# GSSFRAMEWORK_LDFLAGS +# + +FIND_PATH(GSSFRAMEWORK_INCLUDE_DIR NAMES GSS.h) +FIND_LIBRARY(GSSFRAMEWORK_LIBRARIES NAMES GSS) +IF (GSSFRAMEWORK_INCLUDE_DIR AND GSSFRAMEWORK_LIBRARIES) + IF (NOT CoreFoundation_FIND_QUIETLY) + MESSAGE(STATUS "Found GSS.framework ${GSSFRAMEWORK_LIBRARIES}") + ENDIF() + SET(GSSFRAMEWORK_FOUND TRUE) + SET(GSSFRAMEWORK_LDFLAGS "-framework GSS") +ENDIF () + +IF (GSS_FIND_REQUIRED AND NOT GSSFRAMEWORK_FOUND) + MESSAGE(FATAL_ERROR "CoreFoundation not found") +ENDIF() + +MARK_AS_ADVANCED( + GSSFRAMEWORK_INCLUDE_DIR + GSSFRAMEWORK_LIBRARIES + GSSFRAMEWORK_LDFLAGS +) diff --git a/cmake/Modules/FindPCRE.cmake b/cmake/Modules/FindPCRE.cmake new file mode 100644 index 000000000..74ed61e53 --- /dev/null +++ b/cmake/Modules/FindPCRE.cmake @@ -0,0 +1,38 @@ +# Copyright (C) 2007-2009 LuaDist. +# Created by Peter Kapec +# Redistribution and use of this file is allowed according to the terms of the MIT license. +# For details see the COPYRIGHT file distributed with LuaDist. +# Note: +# Searching headers and libraries is very simple and is NOT as powerful as scripts +# distributed with CMake, because LuaDist defines directories to search for. +# Everyone is encouraged to contact the author with improvements. Maybe this file +# becomes part of CMake distribution sometimes. + +# - Find pcre +# Find the native PCRE headers and libraries. +# +# PCRE_INCLUDE_DIRS - where to find pcre.h, etc. +# PCRE_LIBRARIES - List of libraries when using pcre. +# PCRE_FOUND - True if pcre found. + +# Look for the header file. +FIND_PATH(PCRE_INCLUDE_DIR NAMES pcreposix.h) + +# Look for the library. +FIND_LIBRARY(PCRE_LIBRARY NAMES pcre) +FIND_LIBRARY(PCRE_POSIX_LIBRARY NAMES pcreposix) + +# Handle the QUIETLY and REQUIRED arguments and set PCRE_FOUND to TRUE if all listed variables are TRUE. +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(PCRE DEFAULT_MSG PCRE_LIBRARY PCRE_POSIX_LIBRARY PCRE_INCLUDE_DIR) + +# Copy the results to the output variables. +IF(PCRE_FOUND) + SET(PCRE_LIBRARIES ${PCRE_LIBRARY} ${PCRE_POSIX_LIBRARY}) + SET(PCRE_INCLUDE_DIRS ${PCRE_INCLUDE_DIR}) +ELSE(PCRE_FOUND) + SET(PCRE_LIBRARIES) + SET(PCRE_INCLUDE_DIRS) +ENDIF(PCRE_FOUND) + +MARK_AS_ADVANCED(PCRE_INCLUDE_DIRS PCRE_LIBRARIES) diff --git a/cmake/Modules/FindPCRE2.cmake b/cmake/Modules/FindPCRE2.cmake new file mode 100644 index 000000000..f8c5639d5 --- /dev/null +++ b/cmake/Modules/FindPCRE2.cmake @@ -0,0 +1,37 @@ +# Copyright (C) 2007-2009 LuaDist. +# Created by Peter Kapec +# Redistribution and use of this file is allowed according to the terms of the MIT license. +# For details see the COPYRIGHT file distributed with LuaDist. +# Note: +# Searching headers and libraries is very simple and is NOT as powerful as scripts +# distributed with CMake, because LuaDist defines directories to search for. +# Everyone is encouraged to contact the author with improvements. Maybe this file +# becomes part of CMake distribution sometimes. + +# - Find pcre +# Find the native PCRE2 headers and libraries. +# +# PCRE2_INCLUDE_DIRS - where to find pcre.h, etc. +# PCRE2_LIBRARIES - List of libraries when using pcre. +# PCRE2_FOUND - True if pcre found. + +# Look for the header file. +FIND_PATH(PCRE2_INCLUDE_DIR NAMES pcre2posix.h) + +# Look for the library. +FIND_LIBRARY(PCRE2_LIBRARY NAMES pcre2-8) + +# Handle the QUIETLY and REQUIRED arguments and set PCRE2_FOUND to TRUE if all listed variables are TRUE. +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(PCRE2 DEFAULT_MSG PCRE2_LIBRARY PCRE2_INCLUDE_DIR) + +# Copy the results to the output variables. +IF(PCRE2_FOUND) + SET(PCRE2_LIBRARIES ${PCRE2_LIBRARY}) + SET(PCRE2_INCLUDE_DIRS ${PCRE2_INCLUDE_DIR}) +ELSE(PCRE2_FOUND) + SET(PCRE2_LIBRARIES) + SET(PCRE2_INCLUDE_DIRS) +ENDIF(PCRE2_FOUND) + +MARK_AS_ADVANCED(PCRE2_INCLUDE_DIRS PCRE2_LIBRARIES) diff --git a/cmake/Modules/FindSecurity.cmake b/cmake/Modules/FindSecurity.cmake index 487f7e500..a538c02c1 100644 --- a/cmake/Modules/FindSecurity.cmake +++ b/cmake/Modules/FindSecurity.cmake @@ -11,7 +11,7 @@ FIND_PATH(SECURITY_INCLUDE_DIR NAMES Security/Security.h) FIND_LIBRARY(SECURITY_LIBRARIES NAMES Security) IF (SECURITY_INCLUDE_DIR AND SECURITY_LIBRARIES) IF (NOT Security_FIND_QUIETLY) - MESSAGE("-- Found Security ${SECURITY_LIBRARIES}") + MESSAGE(STATUS "Found Security ${SECURITY_LIBRARIES}") ENDIF() SET(SECURITY_FOUND TRUE) SET(SECURITY_LDFLAGS "-framework Security") @@ -19,7 +19,7 @@ IF (SECURITY_INCLUDE_DIR AND SECURITY_LIBRARIES) ENDIF () IF (Security_FIND_REQUIRED AND NOT SECURITY_FOUND) - MESSAGE(FATAL "-- Security not found") + MESSAGE(FATAL_ERROR "Security not found") ENDIF() MARK_AS_ADVANCED( diff --git a/cmake/Modules/FindStatNsec.cmake b/cmake/Modules/FindStatNsec.cmake index fa550a214..a4a09fa81 100644 --- a/cmake/Modules/FindStatNsec.cmake +++ b/cmake/Modules/FindStatNsec.cmake @@ -1,3 +1,5 @@ +INCLUDE(FeatureSummary) + CHECK_STRUCT_HAS_MEMBER ("struct stat" st_mtim "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_MTIM LANGUAGE C) CHECK_STRUCT_HAS_MEMBER ("struct stat" st_mtimespec "sys/types.h;sys/stat.h" @@ -17,4 +19,8 @@ ENDIF() IF (HAVE_STRUCT_STAT_NSEC OR WIN32) OPTION( USE_NSEC "Care about sub-second file mtimes and ctimes" ON ) +ELSE() + SET(USE_NSEC OFF) ENDIF() + +ADD_FEATURE_INFO(nanoseconds USE_NSEC "whether to use sub-second file mtimes and ctimes") diff --git a/cmake/Modules/PkgBuildConfig.cmake b/cmake/Modules/PkgBuildConfig.cmake new file mode 100644 index 000000000..54c5e294c --- /dev/null +++ b/cmake/Modules/PkgBuildConfig.cmake @@ -0,0 +1,77 @@ +# pkg-config file generation +# + +function(pkg_build_config) + set(options) + set(oneValueArgs NAME DESCRIPTION VERSION FILENAME LIBS_SELF) + set(multiValueArgs LIBS PRIVATE_LIBS REQUIRES CFLAGS) + + cmake_parse_arguments(PKGCONFIG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT DEFINED PKGCONFIG_FILENAME AND DEFINED PKGCONFIG_NAME) + set(PKGCONFIG_FILENAME ${PKGCONFIG_NAME}) + endif() + if (NOT DEFINED PKGCONFIG_FILENAME) + message(FATAL_ERROR "Missing FILENAME argument") + endif() + set(PKGCONFIG_FILE "${PROJECT_BINARY_DIR}/${PKGCONFIG_FILENAME}.pc") + + if (NOT DEFINED PKGCONFIG_DESCRIPTION) + message(FATAL_ERROR "Missing DESCRIPTION argument") + endif() + + if (NOT DEFINED PKGCONFIG_VERSION) + message(FATAL_ERROR "Missing VERSION argument") + endif() + + # Write .pc "header" + file(WRITE "${PKGCONFIG_FILE}" + "prefix=\"${CMAKE_INSTALL_PREFIX}\"\n" + "libdir=\"${CMAKE_INSTALL_FULL_LIBDIR}\"\n" + "includedir=\"${CMAKE_INSTALL_FULL_INCLUDEDIR}\"\n" + "\n" + "Name: ${PKGCONFIG_NAME}\n" + "Description: ${PKGCONFIG_DESCRIPTION}\n" + "Version: ${PKGCONFIG_VERSION}\n" + ) + + # Prepare Libs + if(NOT DEFINED PKGCONFIG_LIBS_SELF) + set(PKGCONFIG_LIBS_SELF "${PKGCONFIG_FILE}") + endif() + + if(NOT DEFINED PKGCONFIG_LIBS) + set(PKGCONFIG_LIBS "-l${PKGCONFIG_LIBS_SELF}") + else() + list(INSERT PKGCONFIG_LIBS 0 "-l${PKGCONFIG_LIBS_SELF}") + endif() + + list(REMOVE_DUPLICATES PKGCONFIG_LIBS) + string(REPLACE ";" " " PKGCONFIG_LIBS "${PKGCONFIG_LIBS}") + file(APPEND "${PKGCONFIG_FILE}" "Libs: -L\${libdir} ${PKGCONFIG_LIBS}\n") + + # Prepare Libs.private + if(DEFINED PKGCONFIG_PRIVATE_LIBS) + list(REMOVE_DUPLICATES PKGCONFIG_PRIVATE_LIBS) + string(REPLACE ";" " " PKGCONFIG_PRIVATE_LIBS "${PKGCONFIG_PRIVATE_LIBS}") + file(APPEND "${PKGCONFIG_FILE}" "Libs.private: ${PKGCONFIG_PRIVATE_LIBS}\n") + endif() + + # Prepare Requires.private + if(DEFINED PKGCONFIG_REQUIRES) + list(REMOVE_DUPLICATES PKGCONFIG_REQUIRES) + string(REPLACE ";" " " PKGCONFIG_REQUIRES "${PKGCONFIG_REQUIRES}") + file(APPEND "${PKGCONFIG_FILE}" "Requires.private: ${PKGCONFIG_REQUIRES}\n") + endif() + + # Prepare Cflags + if(DEFINED PKGCONFIG_CFLAGS) + string(REPLACE ";" " " PKGCONFIG_CFLAGS "${PKGCONFIG_CFLAGS}") + else() + set(PKGCONFIG_CFLAGS "") + endif() + file(APPEND "${PKGCONFIG_FILE}" "Cflags: -I\${includedir} ${PKGCONFIG_CFLAGS}\n") + + # Install .pc file + install(FILES "${PKGCONFIG_FILE}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +endfunction() diff --git a/cmake/Modules/SanitizeBool.cmake b/cmake/Modules/SanitizeBool.cmake new file mode 100644 index 000000000..b5b99a690 --- /dev/null +++ b/cmake/Modules/SanitizeBool.cmake @@ -0,0 +1,20 @@ +FUNCTION(SanitizeBool VAR) + STRING(TOLOWER "${${VAR}}" VALUE) + IF(VALUE STREQUAL "on") + SET(${VAR} "ON" PARENT_SCOPE) + ELSEIF(VALUE STREQUAL "yes") + SET(${VAR} "ON" PARENT_SCOPE) + ELSEIF(VALUE STREQUAL "true") + SET(${VAR} "ON" PARENT_SCOPE) + ELSEIF(VALUE STREQUAL "1") + SET(${VAR} "ON" PARENT_SCOPE) + ELSEIF(VALUE STREQUAL "off") + SET(${VAR} "OFF" PARENT_SCOPE) + ELSEIF(VALUE STREQUAL "no") + SET(${VAR} "OFF" PARENT_SCOPE) + ELSEIF(VALUE STREQUAL "false") + SET(${VAR} "OFF" PARENT_SCOPE) + ELSEIF(VALUE STREQUAL "0") + SET(${VAR} "OFF" PARENT_SCOPE) + ENDIF() +ENDFUNCTION() diff --git a/cmake/Modules/SelectGSSAPI.cmake b/cmake/Modules/SelectGSSAPI.cmake new file mode 100644 index 000000000..9b2bb1fc5 --- /dev/null +++ b/cmake/Modules/SelectGSSAPI.cmake @@ -0,0 +1,56 @@ +# Select the backend to use + +# We try to find any packages our backends might use + +INCLUDE(SanitizeBool) + +FIND_PACKAGE(GSSAPI) +IF (CMAKE_SYSTEM_NAME MATCHES "Darwin") + INCLUDE(FindGSSFramework) +ENDIF() + +# Auto-select GSS backend +SanitizeBool(USE_GSSAPI) +IF (USE_GSSAPI STREQUAL ON) + IF (GSSFRAMEWORK_FOUND) + SET(GSS_BACKEND "GSS.framework") + ELSEIF(GSSAPI_FOUND) + SET(GSS_BACKEND "gssapi") + ELSE() + MESSAGE(FATAL_ERROR "Unable to autodetect a usable GSS backend." + "Please pass the backend name explicitly (-DUSE_GSS=backend)") + ENDIF() +ELSEIF(USE_GSSAPI) + # Backend was explicitly set + SET(GSS_BACKEND ${USE_GSSAPI}) +ELSE() + SET(GSS_BACKEND NO) +ENDIF() + +IF(GSS_BACKEND) + # Check that we can find what's required for the selected backend + IF (GSS_BACKEND STREQUAL "GSS.framework") + IF (NOT GSSFRAMEWORK_FOUND) + MESSAGE(FATAL_ERROR "Asked for GSS.framework backend, but it wasn't found") + ENDIF() + + LIST(APPEND LIBGIT2_LIBS ${GSSFRAMEWORK_LIBRARIES}) + + SET(GIT_GSSFRAMEWORK 1) + ADD_FEATURE_INFO(SPNEGO GIT_GSSFRAMEWORK "SPNEGO authentication support (${GSS_BACKEND})") + ELSEIF (GSS_BACKEND STREQUAL "gssapi") + IF (NOT GSSAPI_FOUND) + MESSAGE(FATAL_ERROR "Asked for gssapi GSS backend, but it wasn't found") + ENDIF() + + LIST(APPEND LIBGIT2_LIBS ${GSSAPI_LIBRARIES}) + + SET(GIT_GSSAPI 1) + ADD_FEATURE_INFO(SPNEGO GIT_GSSAPI "SPNEGO authentication support (${GSS_BACKEND})") + ELSE() + MESSAGE(FATAL_ERROR "Asked for backend ${GSS_BACKEND} but it wasn't found") + ENDIF() +ELSE() + SET(GIT_GSSAPI 0) + ADD_FEATURE_INFO(SPNEGO NO "SPNEGO authentication support") +ENDIF() diff --git a/cmake/Modules/SelectHTTPSBackend.cmake b/cmake/Modules/SelectHTTPSBackend.cmake new file mode 100644 index 000000000..f9b0b1cdc --- /dev/null +++ b/cmake/Modules/SelectHTTPSBackend.cmake @@ -0,0 +1,127 @@ +# Select the backend to use + +INCLUDE(SanitizeBool) + +# We try to find any packages our backends might use +FIND_PACKAGE(OpenSSL) +FIND_PACKAGE(mbedTLS) +IF (CMAKE_SYSTEM_NAME MATCHES "Darwin") + FIND_PACKAGE(Security) + FIND_PACKAGE(CoreFoundation) +ENDIF() + +# Auto-select TLS backend +SanitizeBool(USE_HTTPS) +IF (USE_HTTPS STREQUAL ON) + IF (SECURITY_FOUND) + IF (SECURITY_HAS_SSLCREATECONTEXT) + SET(HTTPS_BACKEND "SecureTransport") + ELSE() + MESSAGE(STATUS "Security framework is too old, falling back to OpenSSL") + SET(HTTPS_BACKEND "OpenSSL") + ENDIF() + ELSEIF (WINHTTP) + SET(HTTPS_BACKEND "WinHTTP") + ELSEIF(OPENSSL_FOUND) + SET(HTTPS_BACKEND "OpenSSL") + ELSEIF(MBEDTLS_FOUND) + SET(HTTPS_BACKEND "mbedTLS") + ELSE() + MESSAGE(FATAL_ERROR "Unable to autodetect a usable HTTPS backend." + "Please pass the backend name explicitly (-DUSE_HTTPS=backend)") + ENDIF() +ELSEIF(USE_HTTPS) + # HTTPS backend was explicitly set + SET(HTTPS_BACKEND ${USE_HTTPS}) +ELSE() + SET(HTTPS_BACKEND NO) +ENDIF() + +IF(HTTPS_BACKEND) + # Check that we can find what's required for the selected backend + IF (HTTPS_BACKEND STREQUAL "SecureTransport") + IF (NOT COREFOUNDATION_FOUND) + MESSAGE(FATAL_ERROR "Cannot use SecureTransport backend, CoreFoundation.framework not found") + ENDIF() + IF (NOT SECURITY_FOUND) + MESSAGE(FATAL_ERROR "Cannot use SecureTransport backend, Security.framework not found") + ENDIF() + IF (NOT SECURITY_HAS_SSLCREATECONTEXT) + MESSAGE(FATAL_ERROR "Cannot use SecureTransport backend, SSLCreateContext not supported") + ENDIF() + + SET(GIT_SECURE_TRANSPORT 1) + LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${SECURITY_INCLUDE_DIR}) + LIST(APPEND LIBGIT2_LIBS ${COREFOUNDATION_LDFLAGS} ${SECURITY_LDFLAGS}) + LIST(APPEND LIBGIT2_PC_LIBS ${COREFOUNDATION_LDFLAGS} ${SECURITY_LDFLAGS}) + ELSEIF (HTTPS_BACKEND STREQUAL "OpenSSL") + IF (NOT OPENSSL_FOUND) + MESSAGE(FATAL_ERROR "Asked for OpenSSL TLS backend, but it wasn't found") + ENDIF() + + SET(GIT_OPENSSL 1) + LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${OPENSSL_INCLUDE_DIR}) + LIST(APPEND LIBGIT2_LIBS ${OPENSSL_LIBRARIES}) + LIST(APPEND LIBGIT2_PC_LIBS ${OPENSSL_LDFLAGS}) + LIST(APPEND LIBGIT2_PC_REQUIRES "openssl") + ELSEIF(HTTPS_BACKEND STREQUAL "mbedTLS") + IF (NOT MBEDTLS_FOUND) + MESSAGE(FATAL_ERROR "Asked for mbedTLS backend, but it wasn't found") + ENDIF() + + IF(NOT CERT_LOCATION) + MESSAGE(STATUS "Auto-detecting default certificates location") + IF(CMAKE_SYSTEM_NAME MATCHES Darwin) + # Check for an Homebrew installation + SET(OPENSSL_CMD "/usr/local/opt/openssl/bin/openssl") + ELSE() + SET(OPENSSL_CMD "openssl") + ENDIF() + EXECUTE_PROCESS(COMMAND ${OPENSSL_CMD} version -d OUTPUT_VARIABLE OPENSSL_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) + IF(OPENSSL_DIR) + STRING(REGEX REPLACE "^OPENSSLDIR: \"(.*)\"$" "\\1/" OPENSSL_DIR ${OPENSSL_DIR}) + + SET(OPENSSL_CA_LOCATIONS + "ca-bundle.pem" # OpenSUSE Leap 42.1 + "cert.pem" # Ubuntu 14.04, FreeBSD + "certs/ca-certificates.crt" # Ubuntu 16.04 + "certs/ca.pem" # Debian 7 + ) + FOREACH(SUFFIX IN LISTS OPENSSL_CA_LOCATIONS) + SET(LOC "${OPENSSL_DIR}${SUFFIX}") + IF(NOT CERT_LOCATION AND EXISTS "${OPENSSL_DIR}${SUFFIX}") + SET(CERT_LOCATION ${LOC}) + ENDIF() + ENDFOREACH() + ELSE() + MESSAGE(FATAL_ERROR "Unable to find OpenSSL executable. Please provide default certificate location via CERT_LOCATION") + ENDIF() + ENDIF() + + IF(CERT_LOCATION) + IF(NOT EXISTS ${CERT_LOCATION}) + MESSAGE(FATAL_ERROR "Cannot use CERT_LOCATION=${CERT_LOCATION} as it doesn't exist") + ENDIF() + ADD_FEATURE_INFO(CERT_LOCATION ON "using certificates from ${CERT_LOCATION}") + ADD_DEFINITIONS(-DGIT_DEFAULT_CERT_LOCATION="${CERT_LOCATION}") + ENDIF() + + SET(GIT_MBEDTLS 1) + LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${MBEDTLS_INCLUDE_DIR}) + LIST(APPEND LIBGIT2_LIBS ${MBEDTLS_LIBRARIES}) + # mbedTLS has no pkgconfig file, hence we can't require it + # https://github.com/ARMmbed/mbedtls/issues/228 + # For now, pass its link flags as our own + LIST(APPEND LIBGIT2_PC_LIBS ${MBEDTLS_LIBRARIES}) + ELSEIF (HTTPS_BACKEND STREQUAL "WinHTTP") + # WinHTTP setup was handled in the WinHTTP-specific block above + ELSE() + MESSAGE(FATAL_ERROR "Asked for backend ${HTTPS_BACKEND} but it wasn't found") + ENDIF() + + SET(GIT_HTTPS 1) + ADD_FEATURE_INFO(HTTPS GIT_HTTPS "using ${HTTPS_BACKEND}") +ELSE() + SET(GIT_HTTPS 0) + ADD_FEATURE_INFO(HTTPS NO "") +ENDIF() diff --git a/cmake/Modules/SelectHashes.cmake b/cmake/Modules/SelectHashes.cmake new file mode 100644 index 000000000..a1339c173 --- /dev/null +++ b/cmake/Modules/SelectHashes.cmake @@ -0,0 +1,69 @@ +# Select a hash backend + +INCLUDE(SanitizeBool) + +# USE_SHA1=CollisionDetection(ON)/HTTPS/Generic/OFF + +SanitizeBool(USE_SHA1) +IF(USE_SHA1 STREQUAL ON OR USE_SHA1 STREQUAL "CollisionDetection") + SET(SHA1_BACKEND "CollisionDetection") +ELSEIF(USE_SHA1 STREQUAL "HTTPS") + message(STATUS "Checking HTTPS backend… ${HTTPS_BACKEND}") + IF(HTTPS_BACKEND STREQUAL "SecureTransport") + SET(SHA1_BACKEND "CommonCrypto") + ELSEIF(HTTPS_BACKEND STREQUAL "WinHTTP") + SET(SHA1_BACKEND "Win32") + ELSEIF(HTTPS_BACKEND) + SET(SHA1_BACKEND ${HTTPS_BACKEND}) + ELSE() + ENDIF() + IF(NOT HTTPS_BACKEND) + SET(SHA1_BACKEND "CollisionDetection") + ENDIF() + message(STATUS "Using SHA1 backend ${SHA1_BACKEND}") +ELSEIF(USE_SHA1 STREQUAL "Generic") + SET(SHA1_BACKEND "Generic") +# ELSEIF(NOT USE_SHA1) +ELSE() + MESSAGE(FATAL_ERROR "Invalid value for USE_SHA1: ${USE_SHA1}") +ENDIF() + +IF(SHA1_BACKEND STREQUAL "CollisionDetection") + SET(GIT_SHA1_COLLISIONDETECT 1) + ADD_DEFINITIONS(-DSHA1DC_NO_STANDARD_INCLUDES=1) + ADD_DEFINITIONS(-DSHA1DC_CUSTOM_INCLUDE_SHA1_C=\"common.h\") + ADD_DEFINITIONS(-DSHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C=\"common.h\") + FILE(GLOB SRC_SHA1 hash/sha1/collisiondetect.* hash/sha1/sha1dc/*) +ELSEIF(SHA1_BACKEND STREQUAL "OpenSSL") + # OPENSSL_FOUND should already be set, we're checking HTTPS_BACKEND + + SET(GIT_SHA1_OPENSSL 1) + IF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + LIST(APPEND LIBGIT2_PC_LIBS "-lssl") + ELSE() + LIST(APPEND LIBGIT2_PC_REQUIRES "openssl") + ENDIF() + FILE(GLOB SRC_SHA1 hash/sha1/openssl.*) +ELSEIF(SHA1_BACKEND STREQUAL "CommonCrypto") + SET(GIT_SHA1_COMMON_CRYPTO 1) + FILE(GLOB SRC_SHA1 hash/sha1/common_crypto.*) +ELSEIF(SHA1_BACKEND STREQUAL "mbedTLS") + SET(GIT_SHA1_MBEDTLS 1) + FILE(GLOB SRC_SHA1 hash/sha1/mbedtls.*) + LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${MBEDTLS_INCLUDE_DIR}) + LIST(APPEND LIBGIT2_LIBS ${MBEDTLS_LIBRARIES}) + # mbedTLS has no pkgconfig file, hence we can't require it + # https://github.com/ARMmbed/mbedtls/issues/228 + # For now, pass its link flags as our own + LIST(APPEND LIBGIT2_PC_LIBS ${MBEDTLS_LIBRARIES}) +ELSEIF(SHA1_BACKEND STREQUAL "Win32") + SET(GIT_SHA1_WIN32 1) + FILE(GLOB SRC_SHA1 hash/sha1/win32.*) +ELSEIF(SHA1_BACKEND STREQUAL "Generic") + FILE(GLOB SRC_SHA1 hash/sha1/generic.*) +# ELSEIF(NOT USE_SHA1) +ELSE() + MESSAGE(FATAL_ERROR "Asked for unknown SHA1 backend: ${SHA1_BACKEND}") +ENDIF() + +ADD_FEATURE_INFO(SHA ON "using ${SHA1_BACKEND}") diff --git a/debian/changelog b/debian/changelog index 54f6badf4..4da67eeac 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,56 @@ +libgit2 (1.0.1+dfsg.1-1) experimental; urgency=medium + + [ Utkarsh Gupta ] + * New upstream version 1.0.1+dfsg.1. + * Update symbols file to add git_worktree_prune_init_options@Base. + + [ Debian Janitor ] + * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository, + Repository-Browse. + * Drop transition for old debug package migration. + * Fix day-of-week for changelog entry 0.19.0-2. + + -- Utkarsh Gupta Fri, 09 Oct 2020 21:05:33 +0530 + +libgit2 (1.0.0+dfsg.1-2) experimental; urgency=medium + + [ Utkarsh Gupta ] + * Add Build-Depends-Package: libgit2-dev to libgit2-1.0.symbols + + [ Ximin Luo ] + * Add missing libgit2-dev: Depends: libpcre3-dev. (Closes: #961952) + + -- Ximin Luo Fri, 02 Oct 2020 03:30:36 +0100 + +libgit2 (1.0.0+dfsg.1-1) experimental; urgency=medium + + * New upstream version 1.0.0+dfsg.1 + * Library transition to 1.0.0 + * Drop patch as it is merged upstream + + -- Utkarsh Gupta Fri, 10 Apr 2020 02:21:10 +0530 + +libgit2 (0.99.0+dfsg.1-1) experimental; urgency=medium + + [ Utkarsh Gupta ] + * New upstream version 0.99.0+dfsg.1 + * Use pcre2 as REGEX_BACKEND + * Add patch to fix installation in wrong location + * Add BD on libpcre2-dev + * Add -DUSE_NTLMCLIENT and -DUSE_HTTP_PARSER flags to CMake + * Library transition from 28 to 99 + * Fix package-name-doesnt-match-sonames + * Fix d/copyright wrt the new release + * Add Rules-Requires-Root: no + + [ Sudip Mukherjee ] + * Revert "Use pcre2 as REGEX_BACKEND" + * Add dependency on ca-certificates and libpcre3-dev + * Add ci + * fix symbols file + + -- Utkarsh Gupta Thu, 26 Mar 2020 18:25:16 +0530 + libgit2 (0.28.4+dfsg.1-4) unstable; urgency=medium * Add BD on ca-certificates to fix FTBFS (Closes: #954529) @@ -373,7 +426,7 @@ libgit2 (0.19.0-2) experimental; urgency=low * Multi-arch support. * Manually specified symbols instead of relying on regex. - -- Russell Sim Wed, 20 Aug 2013 22:33:20 +1000 + -- Russell Sim Tue, 20 Aug 2013 22:33:20 +1000 libgit2 (0.19.0-1) experimental; urgency=low diff --git a/debian/control b/debian/control index 18e4589bb..96d5b7fdd 100644 --- a/debian/control +++ b/debian/control @@ -13,22 +13,25 @@ Build-Depends: debhelper-compat (= 12), libmbedtls-dev, libssh2-1-dev, libhttp-parser-dev, - libkrb5-dev + libpcre3-dev, + libkrb5-dev, + ca-certificates Standards-Version: 4.5.0 Homepage: https://libgit2.github.com/ Vcs-Git: https://salsa.debian.org/debian/libgit2.git Vcs-Browser: https://salsa.debian.org/debian/libgit2 - +Rules-Requires-Root: no Package: libgit2-dev Architecture: any Section: libdevel Multi-Arch: same -Depends: libgit2-28 (= ${binary:Version}), +Depends: libgit2-1.0 (= ${binary:Version}), zlib1g-dev, libmbedtls-dev, libssh2-1-dev, libhttp-parser-dev, + libpcre3-dev, ${shlibs:Depends}, ${misc:Depends} Description: low-level Git library (development files) @@ -38,7 +41,7 @@ Description: low-level Git library (development files) . This package contains the development files for libgit2. -Package: libgit2-28 +Package: libgit2-1.0 Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends} diff --git a/debian/copyright b/debian/copyright index b16d15280..92ce25f38 100644 --- a/debian/copyright +++ b/debian/copyright @@ -20,7 +20,7 @@ Files: examples/* Copyright: Public Domain License: CC0-1.0 -Files: include/git2/inttypes.h include/git2/stdint.h +Files: include/git2/stdint.h Copyright: 2006, Alexander Chemeris License: BSD-3-clause-modified @@ -33,7 +33,7 @@ Copyright: 2010, Christopher Swenson 2011, Vicent Marti License: MIT -Files: src/path.c src/fnmatch.h +Files: src/path.c Copyright: 2008, The Android Open Source Project License: BSD-2-clause @@ -64,10 +64,6 @@ Files: src/win32/posix_w32.c Copyright: 1999 - 2012, The PHP Group License: PHP-3.01 -Files: src/fnmatch.c -Copyright: 1989, 1993, 1994, The Regents of the University of California. -License: BSD-3-clause - Files: tests/clar/* Copyright: 2011, Vicent Marti License: MIT diff --git a/debian/gitlab-ci.yml b/debian/gitlab-ci.yml new file mode 100644 index 000000000..260ebbe07 --- /dev/null +++ b/debian/gitlab-ci.yml @@ -0,0 +1,11 @@ +# For more information on what jobs are run see: +# https://salsa.debian.org/salsa-ci-team/pipeline +# +# To enable the jobs, go to your repository (at salsa.debian.org) +# and click over Settings > CI/CD > Expand (in General pipelines). +# In "Custom CI config path" write debian/salsa-ci.yml and click +# in "Save Changes". The CI tests will run after the next commit. +--- +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml diff --git a/debian/libgit2-28.install b/debian/libgit2-1.0.install similarity index 100% rename from debian/libgit2-28.install rename to debian/libgit2-1.0.install diff --git a/debian/libgit2-1.0.symbols b/debian/libgit2-1.0.symbols new file mode 100644 index 000000000..29c69fe6d --- /dev/null +++ b/debian/libgit2-1.0.symbols @@ -0,0 +1,890 @@ +libgit2.so.1.0 libgit2-1.0 #MINVER# +* Build-Depends-Package: libgit2-dev + git_annotated_commit_free@Base 1.0.0 + git_annotated_commit_from_fetchhead@Base 1.0.0 + git_annotated_commit_from_ref@Base 1.0.0 + git_annotated_commit_from_revspec@Base 1.0.0 + git_annotated_commit_id@Base 1.0.0 + git_annotated_commit_lookup@Base 1.0.0 + git_annotated_commit_ref@Base 1.0.0 + git_apply@Base 1.0.0 + git_apply_options_init@Base 1.0.0 + git_apply_to_tree@Base 1.0.0 + git_attr_add_macro@Base 1.0.0 + git_attr_cache_flush@Base 1.0.0 + git_attr_foreach@Base 1.0.0 + git_attr_get@Base 1.0.0 + git_attr_get_many@Base 1.0.0 + git_attr_value@Base 1.0.0 + git_blame_buffer@Base 1.0.0 + git_blame_file@Base 1.0.0 + git_blame_free@Base 1.0.0 + git_blame_get_hunk_byindex@Base 1.0.0 + git_blame_get_hunk_byline@Base 1.0.0 + git_blame_get_hunk_count@Base 1.0.0 + git_blame_init_options@Base 1.0.0 + git_blame_options_init@Base 1.0.0 + git_blob_create_from_buffer@Base 1.0.0 + git_blob_create_from_disk@Base 1.0.0 + git_blob_create_from_stream@Base 1.0.0 + git_blob_create_from_stream_commit@Base 1.0.0 + git_blob_create_from_workdir@Base 1.0.0 + git_blob_create_frombuffer@Base 1.0.0 + git_blob_create_fromdisk@Base 1.0.0 + git_blob_create_fromstream@Base 1.0.0 + git_blob_create_fromstream_commit@Base 1.0.0 + git_blob_create_fromworkdir@Base 1.0.0 + git_blob_dup@Base 1.0.0 + git_blob_filter@Base 1.0.0 + git_blob_filtered_content@Base 1.0.0 + git_blob_free@Base 1.0.0 + git_blob_id@Base 1.0.0 + git_blob_is_binary@Base 1.0.0 + git_blob_lookup@Base 1.0.0 + git_blob_lookup_prefix@Base 1.0.0 + git_blob_owner@Base 1.0.0 + git_blob_rawcontent@Base 1.0.0 + git_blob_rawsize@Base 1.0.0 + git_branch_create@Base 1.0.0 + git_branch_create_from_annotated@Base 1.0.0 + git_branch_delete@Base 1.0.0 + git_branch_is_checked_out@Base 1.0.0 + git_branch_is_head@Base 1.0.0 + git_branch_iterator_free@Base 1.0.0 + git_branch_iterator_new@Base 1.0.0 + git_branch_lookup@Base 1.0.0 + git_branch_move@Base 1.0.0 + git_branch_name@Base 1.0.0 + git_branch_next@Base 1.0.0 + git_branch_remote_name@Base 1.0.0 + git_branch_set_upstream@Base 1.0.0 + git_branch_upstream@Base 1.0.0 + git_branch_upstream_name@Base 1.0.0 + git_branch_upstream_remote@Base 1.0.0 + git_buf_contains_nul@Base 1.0.0 + git_buf_dispose@Base 1.0.0 + git_buf_free@Base 1.0.0 + git_buf_grow@Base 1.0.0 + git_buf_is_binary@Base 1.0.0 + git_buf_set@Base 1.0.0 + git_checkout_head@Base 1.0.0 + git_checkout_index@Base 1.0.0 + git_checkout_init_options@Base 1.0.0 + git_checkout_options_init@Base 1.0.0 + git_checkout_tree@Base 1.0.0 + git_cherrypick@Base 1.0.0 + git_cherrypick_commit@Base 1.0.0 + git_cherrypick_init_options@Base 1.0.0 + git_cherrypick_options_init@Base 1.0.0 + git_clone@Base 1.0.0 + git_clone_init_options@Base 1.0.0 + git_clone_options_init@Base 1.0.0 + git_commit_amend@Base 1.0.0 + git_commit_author@Base 1.0.0 + git_commit_author_with_mailmap@Base 1.0.0 + git_commit_body@Base 1.0.0 + git_commit_committer@Base 1.0.0 + git_commit_committer_with_mailmap@Base 1.0.0 + git_commit_create@Base 1.0.0 + git_commit_create_buffer@Base 1.0.0 + git_commit_create_from_callback@Base 1.0.0 + git_commit_create_from_ids@Base 1.0.0 + git_commit_create_v@Base 1.0.0 + git_commit_create_with_signature@Base 1.0.0 + git_commit_dup@Base 1.0.0 + git_commit_extract_signature@Base 1.0.0 + git_commit_free@Base 1.0.0 + git_commit_header_field@Base 1.0.0 + git_commit_id@Base 1.0.0 + git_commit_lookup@Base 1.0.0 + git_commit_lookup_prefix@Base 1.0.0 + git_commit_message@Base 1.0.0 + git_commit_message_encoding@Base 1.0.0 + git_commit_message_raw@Base 1.0.0 + git_commit_nth_gen_ancestor@Base 1.0.0 + git_commit_owner@Base 1.0.0 + git_commit_parent@Base 1.0.0 + git_commit_parent_id@Base 1.0.0 + git_commit_parentcount@Base 1.0.0 + git_commit_raw_header@Base 1.0.0 + git_commit_summary@Base 1.0.0 + git_commit_time@Base 1.0.0 + git_commit_time_offset@Base 1.0.0 + git_commit_tree@Base 1.0.0 + git_commit_tree_id@Base 1.0.0 + git_config_add_backend@Base 1.0.0 + git_config_add_file_ondisk@Base 1.0.0 + git_config_backend_foreach_match@Base 1.0.0 + git_config_delete_entry@Base 1.0.0 + git_config_delete_multivar@Base 1.0.0 + git_config_entry_free@Base 1.0.0 + git_config_find_global@Base 1.0.0 + git_config_find_programdata@Base 1.0.0 + git_config_find_system@Base 1.0.0 + git_config_find_xdg@Base 1.0.0 + git_config_foreach@Base 1.0.0 + git_config_foreach_match@Base 1.0.0 + git_config_free@Base 1.0.0 + git_config_get_bool@Base 1.0.0 + git_config_get_entry@Base 1.0.0 + git_config_get_int32@Base 1.0.0 + git_config_get_int64@Base 1.0.0 + git_config_get_mapped@Base 1.0.0 + git_config_get_multivar_foreach@Base 1.0.0 + git_config_get_path@Base 1.0.0 + git_config_get_string@Base 1.0.0 + git_config_get_string_buf@Base 1.0.0 + git_config_init_backend@Base 1.0.0 + git_config_iterator_free@Base 1.0.0 + git_config_iterator_glob_new@Base 1.0.0 + git_config_iterator_new@Base 1.0.0 + git_config_lock@Base 1.0.0 + git_config_lookup_map_value@Base 1.0.0 + git_config_multivar_iterator_new@Base 1.0.0 + git_config_new@Base 1.0.0 + git_config_next@Base 1.0.0 + git_config_open_default@Base 1.0.0 + git_config_open_global@Base 1.0.0 + git_config_open_level@Base 1.0.0 + git_config_open_ondisk@Base 1.0.0 + git_config_parse_bool@Base 1.0.0 + git_config_parse_int32@Base 1.0.0 + git_config_parse_int64@Base 1.0.0 + git_config_parse_path@Base 1.0.0 + git_config_set_bool@Base 1.0.0 + git_config_set_int32@Base 1.0.0 + git_config_set_int64@Base 1.0.0 + git_config_set_multivar@Base 1.0.0 + git_config_set_string@Base 1.0.0 + git_config_snapshot@Base 1.0.0 + git_config_unlock@Base 1.0.0 + git_cred_default_new@Base 1.0.0 + git_cred_free@Base 1.0.0 + git_cred_get_username@Base 1.0.0 + git_cred_has_username@Base 1.0.0 + git_cred_ssh_custom_new@Base 1.0.0 + git_cred_ssh_interactive_new@Base 1.0.0 + git_cred_ssh_key_from_agent@Base 1.0.0 + git_cred_ssh_key_memory_new@Base 1.0.0 + git_cred_ssh_key_new@Base 1.0.0 + git_cred_username_new@Base 1.0.0 + git_cred_userpass@Base 1.0.0 + git_cred_userpass_plaintext_new@Base 1.0.0 + git_credential_default_new@Base 1.0.0 + git_credential_free@Base 1.0.0 + git_credential_get_username@Base 1.0.0 + git_credential_has_username@Base 1.0.0 + git_credential_ssh_custom_new@Base 1.0.0 + git_credential_ssh_interactive_new@Base 1.0.0 + git_credential_ssh_key_from_agent@Base 1.0.0 + git_credential_ssh_key_memory_new@Base 1.0.0 + git_credential_ssh_key_new@Base 1.0.0 + git_credential_username_new@Base 1.0.0 + git_credential_userpass@Base 1.0.0 + git_credential_userpass_plaintext_new@Base 1.0.0 + git_describe_commit@Base 1.0.0 + git_describe_format@Base 1.0.0 + git_describe_format_options_init@Base 1.0.0 + git_describe_init_format_options@Base 1.0.0 + git_describe_init_options@Base 1.0.0 + git_describe_options_init@Base 1.0.0 + git_describe_result_free@Base 1.0.0 + git_describe_workdir@Base 1.0.0 + git_diff_blob_to_buffer@Base 1.0.0 + git_diff_blobs@Base 1.0.0 + git_diff_buffers@Base 1.0.0 + git_diff_commit_as_email@Base 1.0.0 + git_diff_find_init_options@Base 1.0.0 + git_diff_find_options_init@Base 1.0.0 + git_diff_find_similar@Base 1.0.0 + git_diff_foreach@Base 1.0.0 + git_diff_format_email@Base 1.0.0 + git_diff_format_email_init_options@Base 1.0.0 + git_diff_format_email_options_init@Base 1.0.0 + git_diff_free@Base 1.0.0 + git_diff_from_buffer@Base 1.0.0 + git_diff_get_delta@Base 1.0.0 + git_diff_get_perfdata@Base 1.0.0 + git_diff_get_stats@Base 1.0.0 + git_diff_index_to_index@Base 1.0.0 + git_diff_index_to_workdir@Base 1.0.0 + git_diff_init_options@Base 1.0.0 + git_diff_is_sorted_icase@Base 1.0.0 + git_diff_merge@Base 1.0.0 + git_diff_num_deltas@Base 1.0.0 + git_diff_num_deltas_of_type@Base 1.0.0 + git_diff_options_init@Base 1.0.0 + git_diff_patchid@Base 1.0.0 + git_diff_patchid_options_init@Base 1.0.0 + git_diff_print@Base 1.0.0 + git_diff_print_callback__to_buf@Base 1.0.0 + git_diff_print_callback__to_file_handle@Base 1.0.0 + git_diff_stats_deletions@Base 1.0.0 + git_diff_stats_files_changed@Base 1.0.0 + git_diff_stats_free@Base 1.0.0 + git_diff_stats_insertions@Base 1.0.0 + git_diff_stats_to_buf@Base 1.0.0 + git_diff_status_char@Base 1.0.0 + git_diff_to_buf@Base 1.0.0 + git_diff_tree_to_index@Base 1.0.0 + git_diff_tree_to_tree@Base 1.0.0 + git_diff_tree_to_workdir@Base 1.0.0 + git_diff_tree_to_workdir_with_index@Base 1.0.0 + git_error_clear@Base 1.0.0 + git_error_last@Base 1.0.0 + git_error_set_oom@Base 1.0.0 + git_error_set_str@Base 1.0.0 + git_fetch_init_options@Base 1.0.0 + git_fetch_options_init@Base 1.0.0 + git_filter_init@Base 1.0.0 + git_filter_list_apply_to_blob@Base 1.0.0 + git_filter_list_apply_to_data@Base 1.0.0 + git_filter_list_apply_to_file@Base 1.0.0 + git_filter_list_contains@Base 1.0.0 + git_filter_list_free@Base 1.0.0 + git_filter_list_length@Base 1.0.0 + git_filter_list_load@Base 1.0.0 + git_filter_list_new@Base 1.0.0 + git_filter_list_push@Base 1.0.0 + git_filter_list_stream_blob@Base 1.0.0 + git_filter_list_stream_data@Base 1.0.0 + git_filter_list_stream_file@Base 1.0.0 + git_filter_lookup@Base 1.0.0 + git_filter_register@Base 1.0.0 + git_filter_source_filemode@Base 1.0.0 + git_filter_source_flags@Base 1.0.0 + git_filter_source_id@Base 1.0.0 + git_filter_source_mode@Base 1.0.0 + git_filter_source_path@Base 1.0.0 + git_filter_source_repo@Base 1.0.0 + git_filter_unregister@Base 1.0.0 + git_graph_ahead_behind@Base 1.0.0 + git_graph_descendant_of@Base 1.0.0 + git_hashsig_compare@Base 1.0.0 + git_hashsig_create@Base 1.0.0 + git_hashsig_create_fromfile@Base 1.0.0 + git_hashsig_free@Base 1.0.0 + git_ignore_add_rule@Base 1.0.0 + git_ignore_clear_internal_rules@Base 1.0.0 + git_ignore_path_is_ignored@Base 1.0.0 + git_index_add@Base 1.0.0 + git_index_add_all@Base 1.0.0 + git_index_add_bypath@Base 1.0.0 + git_index_add_from_buffer@Base 1.0.0 + git_index_add_frombuffer@Base 1.0.0 + git_index_caps@Base 1.0.0 + git_index_checksum@Base 1.0.0 + git_index_clear@Base 1.0.0 + git_index_conflict_add@Base 1.0.0 + git_index_conflict_cleanup@Base 1.0.0 + git_index_conflict_get@Base 1.0.0 + git_index_conflict_iterator_free@Base 1.0.0 + git_index_conflict_iterator_new@Base 1.0.0 + git_index_conflict_next@Base 1.0.0 + git_index_conflict_remove@Base 1.0.0 + git_index_entry_is_conflict@Base 1.0.0 + git_index_entry_stage@Base 1.0.0 + git_index_entrycount@Base 1.0.0 + git_index_find@Base 1.0.0 + git_index_find_prefix@Base 1.0.0 + git_index_free@Base 1.0.0 + git_index_get_byindex@Base 1.0.0 + git_index_get_bypath@Base 1.0.0 + git_index_has_conflicts@Base 1.0.0 + git_index_iterator_free@Base 1.0.0 + git_index_iterator_new@Base 1.0.0 + git_index_iterator_next@Base 1.0.0 + git_index_name_add@Base 1.0.0 + git_index_name_clear@Base 1.0.0 + git_index_name_entrycount@Base 1.0.0 + git_index_name_get_byindex@Base 1.0.0 + git_index_new@Base 1.0.0 + git_index_open@Base 1.0.0 + git_index_owner@Base 1.0.0 + git_index_path@Base 1.0.0 + git_index_read@Base 1.0.0 + git_index_read_tree@Base 1.0.0 + git_index_remove@Base 1.0.0 + git_index_remove_all@Base 1.0.0 + git_index_remove_bypath@Base 1.0.0 + git_index_remove_directory@Base 1.0.0 + git_index_reuc_add@Base 1.0.0 + git_index_reuc_clear@Base 1.0.0 + git_index_reuc_entrycount@Base 1.0.0 + git_index_reuc_find@Base 1.0.0 + git_index_reuc_get_byindex@Base 1.0.0 + git_index_reuc_get_bypath@Base 1.0.0 + git_index_reuc_remove@Base 1.0.0 + git_index_set_caps@Base 1.0.0 + git_index_set_version@Base 1.0.0 + git_index_update_all@Base 1.0.0 + git_index_version@Base 1.0.0 + git_index_write@Base 1.0.0 + git_index_write_tree@Base 1.0.0 + git_index_write_tree_to@Base 1.0.0 + git_indexer_append@Base 1.0.0 + git_indexer_commit@Base 1.0.0 + git_indexer_free@Base 1.0.0 + git_indexer_hash@Base 1.0.0 + git_indexer_init_options@Base 1.0.0 + git_indexer_new@Base 1.0.0 + git_indexer_options_init@Base 1.0.0 + git_libgit2_features@Base 1.0.0 + git_libgit2_init@Base 1.0.0 + git_libgit2_opts@Base 1.0.0 + git_libgit2_shutdown@Base 1.0.0 + git_libgit2_version@Base 1.0.0 + git_mailmap_add_entry@Base 1.0.0 + git_mailmap_free@Base 1.0.0 + git_mailmap_from_buffer@Base 1.0.0 + git_mailmap_from_repository@Base 1.0.0 + git_mailmap_new@Base 1.0.0 + git_mailmap_resolve@Base 1.0.0 + git_mailmap_resolve_signature@Base 1.0.0 + git_mempack_dump@Base 1.0.0 + git_mempack_new@Base 1.0.0 + git_mempack_reset@Base 1.0.0 + git_merge@Base 1.0.0 + git_merge_analysis@Base 1.0.0 + git_merge_analysis_for_ref@Base 1.0.0 + git_merge_base@Base 1.0.0 + git_merge_base_many@Base 1.0.0 + git_merge_base_octopus@Base 1.0.0 + git_merge_bases@Base 1.0.0 + git_merge_bases_many@Base 1.0.0 + git_merge_commits@Base 1.0.0 + git_merge_driver_lookup@Base 1.0.0 + git_merge_driver_register@Base 1.0.0 + git_merge_driver_source_ancestor@Base 1.0.0 + git_merge_driver_source_file_options@Base 1.0.0 + git_merge_driver_source_ours@Base 1.0.0 + git_merge_driver_source_repo@Base 1.0.0 + git_merge_driver_source_theirs@Base 1.0.0 + git_merge_driver_unregister@Base 1.0.0 + git_merge_file@Base 1.0.0 + git_merge_file_from_index@Base 1.0.0 + git_merge_file_init_input@Base 1.0.0 + git_merge_file_init_options@Base 1.0.0 + git_merge_file_input_init@Base 1.0.0 + git_merge_file_options_init@Base 1.0.0 + git_merge_file_result_free@Base 1.0.0 + git_merge_init_options@Base 1.0.0 + git_merge_options_init@Base 1.0.0 + git_merge_trees@Base 1.0.0 + git_message_prettify@Base 1.0.0 + git_message_trailer_array_free@Base 1.0.0 + git_message_trailers@Base 1.0.0 + git_note_author@Base 1.0.0 + git_note_commit_create@Base 1.0.0 + git_note_commit_iterator_new@Base 1.0.0 + git_note_commit_read@Base 1.0.0 + git_note_commit_remove@Base 1.0.0 + git_note_committer@Base 1.0.0 + git_note_create@Base 1.0.0 + git_note_default_ref@Base 1.0.0 + git_note_foreach@Base 1.0.0 + git_note_free@Base 1.0.0 + git_note_id@Base 1.0.0 + git_note_iterator_free@Base 1.0.0 + git_note_iterator_new@Base 1.0.0 + git_note_message@Base 1.0.0 + git_note_next@Base 1.0.0 + git_note_read@Base 1.0.0 + git_note_remove@Base 1.0.0 + git_object__size@Base 1.0.0 + git_object_dup@Base 1.0.0 + git_object_free@Base 1.0.0 + git_object_id@Base 1.0.0 + git_object_lookup@Base 1.0.0 + git_object_lookup_bypath@Base 1.0.0 + git_object_lookup_prefix@Base 1.0.0 + git_object_owner@Base 1.0.0 + git_object_peel@Base 1.0.0 + git_object_short_id@Base 1.0.0 + git_object_string2type@Base 1.0.0 + git_object_type2string@Base 1.0.0 + git_object_type@Base 1.0.0 + git_object_typeisloose@Base 1.0.0 + git_odb_add_alternate@Base 1.0.0 + git_odb_add_backend@Base 1.0.0 + git_odb_add_disk_alternate@Base 1.0.0 + git_odb_backend_data_alloc@Base 1.0.0 + git_odb_backend_data_free@Base 1.0.0 + git_odb_backend_loose@Base 1.0.0 + git_odb_backend_malloc@Base 1.0.0 + git_odb_backend_one_pack@Base 1.0.0 + git_odb_backend_pack@Base 1.0.0 + git_odb_exists@Base 1.0.0 + git_odb_exists_prefix@Base 1.0.0 + git_odb_expand_ids@Base 1.0.0 + git_odb_foreach@Base 1.0.0 + git_odb_free@Base 1.0.0 + git_odb_get_backend@Base 1.0.0 + git_odb_hash@Base 1.0.0 + git_odb_hashfile@Base 1.0.0 + git_odb_init_backend@Base 1.0.0 + git_odb_new@Base 1.0.0 + git_odb_num_backends@Base 1.0.0 + git_odb_object_data@Base 1.0.0 + git_odb_object_dup@Base 1.0.0 + git_odb_object_free@Base 1.0.0 + git_odb_object_id@Base 1.0.0 + git_odb_object_size@Base 1.0.0 + git_odb_object_type@Base 1.0.0 + git_odb_open@Base 1.0.0 + git_odb_open_rstream@Base 1.0.0 + git_odb_open_wstream@Base 1.0.0 + git_odb_read@Base 1.0.0 + git_odb_read_header@Base 1.0.0 + git_odb_read_prefix@Base 1.0.0 + git_odb_refresh@Base 1.0.0 + git_odb_stream_finalize_write@Base 1.0.0 + git_odb_stream_free@Base 1.0.0 + git_odb_stream_read@Base 1.0.0 + git_odb_stream_write@Base 1.0.0 + git_odb_write@Base 1.0.0 + git_odb_write_pack@Base 1.0.0 + git_oid_cmp@Base 1.0.0 + git_oid_cpy@Base 1.0.0 + git_oid_equal@Base 1.0.0 + git_oid_fmt@Base 1.0.0 + git_oid_fromraw@Base 1.0.0 + git_oid_fromstr@Base 1.0.0 + git_oid_fromstrn@Base 1.0.0 + git_oid_fromstrp@Base 1.0.0 + git_oid_is_zero@Base 1.0.0 + git_oid_iszero@Base 1.0.0 + git_oid_ncmp@Base 1.0.0 + git_oid_nfmt@Base 1.0.0 + git_oid_pathfmt@Base 1.0.0 + git_oid_shorten_add@Base 1.0.0 + git_oid_shorten_free@Base 1.0.0 + git_oid_shorten_new@Base 1.0.0 + git_oid_strcmp@Base 1.0.0 + git_oid_streq@Base 1.0.0 + git_oid_tostr@Base 1.0.0 + git_oid_tostr_s@Base 1.0.0 + git_oidarray_free@Base 1.0.0 + git_openssl_set_locking@Base 1.0.0 + git_packbuilder_foreach@Base 1.0.0 + git_packbuilder_free@Base 1.0.0 + git_packbuilder_hash@Base 1.0.0 + git_packbuilder_insert@Base 1.0.0 + git_packbuilder_insert_commit@Base 1.0.0 + git_packbuilder_insert_recur@Base 1.0.0 + git_packbuilder_insert_tree@Base 1.0.0 + git_packbuilder_insert_walk@Base 1.0.0 + git_packbuilder_new@Base 1.0.0 + git_packbuilder_object_count@Base 1.0.0 + git_packbuilder_set_callbacks@Base 1.0.0 + git_packbuilder_set_threads@Base 1.0.0 + git_packbuilder_write@Base 1.0.0 + git_packbuilder_write_buf@Base 1.0.0 + git_packbuilder_written@Base 1.0.0 + git_patch_free@Base 1.0.0 + git_patch_from_blob_and_buffer@Base 1.0.0 + git_patch_from_blobs@Base 1.0.0 + git_patch_from_buffers@Base 1.0.0 + git_patch_from_diff@Base 1.0.0 + git_patch_get_delta@Base 1.0.0 + git_patch_get_hunk@Base 1.0.0 + git_patch_get_line_in_hunk@Base 1.0.0 + git_patch_line_stats@Base 1.0.0 + git_patch_num_hunks@Base 1.0.0 + git_patch_num_lines_in_hunk@Base 1.0.0 + git_patch_print@Base 1.0.0 + git_patch_size@Base 1.0.0 + git_patch_to_buf@Base 1.0.0 + git_path_is_gitfile@Base 1.0.0 + git_pathspec_free@Base 1.0.0 + git_pathspec_match_diff@Base 1.0.0 + git_pathspec_match_index@Base 1.0.0 + git_pathspec_match_list_diff_entry@Base 1.0.0 + git_pathspec_match_list_entry@Base 1.0.0 + git_pathspec_match_list_entrycount@Base 1.0.0 + git_pathspec_match_list_failed_entry@Base 1.0.0 + git_pathspec_match_list_failed_entrycount@Base 1.0.0 + git_pathspec_match_list_free@Base 1.0.0 + git_pathspec_match_tree@Base 1.0.0 + git_pathspec_match_workdir@Base 1.0.0 + git_pathspec_matches_path@Base 1.0.0 + git_pathspec_new@Base 1.0.0 + git_proxy_init_options@Base 1.0.0 + git_proxy_options_init@Base 1.0.0 + git_push_init_options@Base 1.0.0 + git_push_options_init@Base 1.0.0 + git_rebase_abort@Base 1.0.0 + git_rebase_commit@Base 1.0.0 + git_rebase_finish@Base 1.0.0 + git_rebase_free@Base 1.0.0 + git_rebase_init@Base 1.0.0 + git_rebase_init_options@Base 1.0.0 + git_rebase_inmemory_index@Base 1.0.0 + git_rebase_next@Base 1.0.0 + git_rebase_onto_id@Base 1.0.0 + git_rebase_onto_name@Base 1.0.0 + git_rebase_open@Base 1.0.0 + git_rebase_operation_byindex@Base 1.0.0 + git_rebase_operation_current@Base 1.0.0 + git_rebase_operation_entrycount@Base 1.0.0 + git_rebase_options_init@Base 1.0.0 + git_rebase_orig_head_id@Base 1.0.0 + git_rebase_orig_head_name@Base 1.0.0 + git_refdb_backend_fs@Base 1.0.0 + git_refdb_compress@Base 1.0.0 + git_refdb_free@Base 1.0.0 + git_refdb_init_backend@Base 1.0.0 + git_refdb_new@Base 1.0.0 + git_refdb_open@Base 1.0.0 + git_refdb_set_backend@Base 1.0.0 + git_reference__alloc@Base 1.0.0 + git_reference__alloc_symbolic@Base 1.0.0 + git_reference_cmp@Base 1.0.0 + git_reference_create@Base 1.0.0 + git_reference_create_matching@Base 1.0.0 + git_reference_delete@Base 1.0.0 + git_reference_dup@Base 1.0.0 + git_reference_dwim@Base 1.0.0 + git_reference_ensure_log@Base 1.0.0 + git_reference_foreach@Base 1.0.0 + git_reference_foreach_glob@Base 1.0.0 + git_reference_foreach_name@Base 1.0.0 + git_reference_free@Base 1.0.0 + git_reference_has_log@Base 1.0.0 + git_reference_is_branch@Base 1.0.0 + git_reference_is_note@Base 1.0.0 + git_reference_is_remote@Base 1.0.0 + git_reference_is_tag@Base 1.0.0 + git_reference_is_valid_name@Base 1.0.0 + git_reference_iterator_free@Base 1.0.0 + git_reference_iterator_glob_new@Base 1.0.0 + git_reference_iterator_new@Base 1.0.0 + git_reference_list@Base 1.0.0 + git_reference_lookup@Base 1.0.0 + git_reference_name@Base 1.0.0 + git_reference_name_to_id@Base 1.0.0 + git_reference_next@Base 1.0.0 + git_reference_next_name@Base 1.0.0 + git_reference_normalize_name@Base 1.0.0 + git_reference_owner@Base 1.0.0 + git_reference_peel@Base 1.0.0 + git_reference_remove@Base 1.0.0 + git_reference_rename@Base 1.0.0 + git_reference_resolve@Base 1.0.0 + git_reference_set_target@Base 1.0.0 + git_reference_shorthand@Base 1.0.0 + git_reference_symbolic_create@Base 1.0.0 + git_reference_symbolic_create_matching@Base 1.0.0 + git_reference_symbolic_set_target@Base 1.0.0 + git_reference_symbolic_target@Base 1.0.0 + git_reference_target@Base 1.0.0 + git_reference_target_peel@Base 1.0.0 + git_reference_type@Base 1.0.0 + git_reflog_append@Base 1.0.0 + git_reflog_delete@Base 1.0.0 + git_reflog_drop@Base 1.0.0 + git_reflog_entry_byindex@Base 1.0.0 + git_reflog_entry_committer@Base 1.0.0 + git_reflog_entry_id_new@Base 1.0.0 + git_reflog_entry_id_old@Base 1.0.0 + git_reflog_entry_message@Base 1.0.0 + git_reflog_entrycount@Base 1.0.0 + git_reflog_free@Base 1.0.0 + git_reflog_read@Base 1.0.0 + git_reflog_rename@Base 1.0.0 + git_reflog_write@Base 1.0.0 + git_refspec_direction@Base 1.0.0 + git_refspec_dst@Base 1.0.0 + git_refspec_dst_matches@Base 1.0.0 + git_refspec_force@Base 1.0.0 + git_refspec_free@Base 1.0.0 + git_refspec_parse@Base 1.0.0 + git_refspec_rtransform@Base 1.0.0 + git_refspec_src@Base 1.0.0 + git_refspec_src_matches@Base 1.0.0 + git_refspec_string@Base 1.0.0 + git_refspec_transform@Base 1.0.0 + git_remote_add_fetch@Base 1.0.0 + git_remote_add_push@Base 1.0.0 + git_remote_autotag@Base 1.0.0 + git_remote_connect@Base 1.0.0 + git_remote_connected@Base 1.0.0 + git_remote_create@Base 1.0.0 + git_remote_create_anonymous@Base 1.0.0 + git_remote_create_detached@Base 1.0.0 + git_remote_create_init_options@Base 1.0.0 + git_remote_create_options_init@Base 1.0.0 + git_remote_create_with_fetchspec@Base 1.0.0 + git_remote_create_with_opts@Base 1.0.0 + git_remote_default_branch@Base 1.0.0 + git_remote_delete@Base 1.0.0 + git_remote_disconnect@Base 1.0.0 + git_remote_download@Base 1.0.0 + git_remote_dup@Base 1.0.0 + git_remote_fetch@Base 1.0.0 + git_remote_free@Base 1.0.0 + git_remote_get_fetch_refspecs@Base 1.0.0 + git_remote_get_push_refspecs@Base 1.0.0 + git_remote_get_refspec@Base 1.0.0 + git_remote_init_callbacks@Base 1.0.0 + git_remote_is_valid_name@Base 1.0.0 + git_remote_list@Base 1.0.0 + git_remote_lookup@Base 1.0.0 + git_remote_ls@Base 1.0.0 + git_remote_name@Base 1.0.0 + git_remote_owner@Base 1.0.0 + git_remote_prune@Base 1.0.0 + git_remote_prune_refs@Base 1.0.0 + git_remote_push@Base 1.0.0 + git_remote_pushurl@Base 1.0.0 + git_remote_refspec_count@Base 1.0.0 + git_remote_rename@Base 1.0.0 + git_remote_set_autotag@Base 1.0.0 + git_remote_set_pushurl@Base 1.0.0 + git_remote_set_url@Base 1.0.0 + git_remote_stats@Base 1.0.0 + git_remote_stop@Base 1.0.0 + git_remote_update_tips@Base 1.0.0 + git_remote_upload@Base 1.0.0 + git_remote_url@Base 1.0.0 + git_repository__cleanup@Base 1.0.0 + git_repository_commondir@Base 1.0.0 + git_repository_config@Base 1.0.0 + git_repository_config_snapshot@Base 1.0.0 + git_repository_detach_head@Base 1.0.0 + git_repository_discover@Base 1.0.0 + git_repository_fetchhead_foreach@Base 1.0.0 + git_repository_free@Base 1.0.0 + git_repository_get_namespace@Base 1.0.0 + git_repository_hashfile@Base 1.0.0 + git_repository_head@Base 1.0.0 + git_repository_head_detached@Base 1.0.0 + git_repository_head_detached_for_worktree@Base 1.0.0 + git_repository_head_for_worktree@Base 1.0.0 + git_repository_head_unborn@Base 1.0.0 + git_repository_ident@Base 1.0.0 + git_repository_index@Base 1.0.0 + git_repository_init@Base 1.0.0 + git_repository_init_ext@Base 1.0.0 + git_repository_init_init_options@Base 1.0.0 + git_repository_init_options_init@Base 1.0.0 + git_repository_is_bare@Base 1.0.0 + git_repository_is_empty@Base 1.0.0 + git_repository_is_shallow@Base 1.0.0 + git_repository_is_worktree@Base 1.0.0 + git_repository_item_path@Base 1.0.0 + git_repository_mergehead_foreach@Base 1.0.0 + git_repository_message@Base 1.0.0 + git_repository_message_remove@Base 1.0.0 + git_repository_new@Base 1.0.0 + git_repository_odb@Base 1.0.0 + git_repository_open@Base 1.0.0 + git_repository_open_bare@Base 1.0.0 + git_repository_open_ext@Base 1.0.0 + git_repository_open_from_worktree@Base 1.0.0 + git_repository_path@Base 1.0.0 + git_repository_refdb@Base 1.0.0 + git_repository_reinit_filesystem@Base 1.0.0 + git_repository_set_bare@Base 1.0.0 + git_repository_set_config@Base 1.0.0 + git_repository_set_head@Base 1.0.0 + git_repository_set_head_detached@Base 1.0.0 + git_repository_set_head_detached_from_annotated@Base 1.0.0 + git_repository_set_ident@Base 1.0.0 + git_repository_set_index@Base 1.0.0 + git_repository_set_namespace@Base 1.0.0 + git_repository_set_odb@Base 1.0.0 + git_repository_set_refdb@Base 1.0.0 + git_repository_set_workdir@Base 1.0.0 + git_repository_state@Base 1.0.0 + git_repository_state_cleanup@Base 1.0.0 + git_repository_submodule_cache_all@Base 1.0.0 + git_repository_submodule_cache_clear@Base 1.0.0 + git_repository_workdir@Base 1.0.0 + git_repository_wrap_odb@Base 1.0.0 + git_reset@Base 1.0.0 + git_reset_default@Base 1.0.0 + git_reset_from_annotated@Base 1.0.0 + git_revert@Base 1.0.0 + git_revert_commit@Base 1.0.0 + git_revert_init_options@Base 1.0.0 + git_revert_options_init@Base 1.0.0 + git_revparse@Base 1.0.0 + git_revparse_ext@Base 1.0.0 + git_revparse_single@Base 1.0.0 + git_revwalk_add_hide_cb@Base 1.0.0 + git_revwalk_free@Base 1.0.0 + git_revwalk_hide@Base 1.0.0 + git_revwalk_hide_glob@Base 1.0.0 + git_revwalk_hide_head@Base 1.0.0 + git_revwalk_hide_ref@Base 1.0.0 + git_revwalk_new@Base 1.0.0 + git_revwalk_next@Base 1.0.0 + git_revwalk_push@Base 1.0.0 + git_revwalk_push_glob@Base 1.0.0 + git_revwalk_push_head@Base 1.0.0 + git_revwalk_push_range@Base 1.0.0 + git_revwalk_push_ref@Base 1.0.0 + git_revwalk_repository@Base 1.0.0 + git_revwalk_reset@Base 1.0.0 + git_revwalk_simplify_first_parent@Base 1.0.0 + git_revwalk_sorting@Base 1.0.0 + git_signature_default@Base 1.0.0 + git_signature_dup@Base 1.0.0 + git_signature_free@Base 1.0.0 + git_signature_from_buffer@Base 1.0.0 + git_signature_new@Base 1.0.0 + git_signature_now@Base 1.0.0 + git_smart_subtransport_git@Base 1.0.0 + git_smart_subtransport_http@Base 1.0.0 + git_smart_subtransport_ssh@Base 1.0.0 + git_stash_apply@Base 1.0.0 + git_stash_apply_init_options@Base 1.0.0 + git_stash_apply_options_init@Base 1.0.0 + git_stash_drop@Base 1.0.0 + git_stash_foreach@Base 1.0.0 + git_stash_pop@Base 1.0.0 + git_stash_save@Base 1.0.0 + git_status_byindex@Base 1.0.0 + git_status_file@Base 1.0.0 + git_status_foreach@Base 1.0.0 + git_status_foreach_ext@Base 1.0.0 + git_status_init_options@Base 1.0.0 + git_status_list_entrycount@Base 1.0.0 + git_status_list_free@Base 1.0.0 + git_status_list_get_perfdata@Base 1.0.0 + git_status_list_new@Base 1.0.0 + git_status_options_init@Base 1.0.0 + git_status_should_ignore@Base 1.0.0 + git_strarray_copy@Base 1.0.0 + git_strarray_free@Base 1.0.0 + git_stream_register@Base 1.0.0 + git_stream_register_tls@Base 1.0.0 + git_submodule_add_finalize@Base 1.0.0 + git_submodule_add_setup@Base 1.0.0 + git_submodule_add_to_index@Base 1.0.0 + git_submodule_branch@Base 1.0.0 + git_submodule_clone@Base 1.0.0 + git_submodule_fetch_recurse_submodules@Base 1.0.0 + git_submodule_foreach@Base 1.0.0 + git_submodule_free@Base 1.0.0 + git_submodule_head_id@Base 1.0.0 + git_submodule_ignore@Base 1.0.0 + git_submodule_index_id@Base 1.0.0 + git_submodule_init@Base 1.0.0 + git_submodule_location@Base 1.0.0 + git_submodule_lookup@Base 1.0.0 + git_submodule_name@Base 1.0.0 + git_submodule_open@Base 1.0.0 + git_submodule_owner@Base 1.0.0 + git_submodule_path@Base 1.0.0 + git_submodule_reload@Base 1.0.0 + git_submodule_repo_init@Base 1.0.0 + git_submodule_resolve_url@Base 1.0.0 + git_submodule_set_branch@Base 1.0.0 + git_submodule_set_fetch_recurse_submodules@Base 1.0.0 + git_submodule_set_ignore@Base 1.0.0 + git_submodule_set_update@Base 1.0.0 + git_submodule_set_url@Base 1.0.0 + git_submodule_status@Base 1.0.0 + git_submodule_sync@Base 1.0.0 + git_submodule_update@Base 1.0.0 + git_submodule_update_init_options@Base 1.0.0 + git_submodule_update_options_init@Base 1.0.0 + git_submodule_update_strategy@Base 1.0.0 + git_submodule_url@Base 1.0.0 + git_submodule_wd_id@Base 1.0.0 + git_tag_annotation_create@Base 1.0.0 + git_tag_create@Base 1.0.0 + git_tag_create_from_buffer@Base 1.0.0 + git_tag_create_frombuffer@Base 1.0.0 + git_tag_create_lightweight@Base 1.0.0 + git_tag_delete@Base 1.0.0 + git_tag_dup@Base 1.0.0 + git_tag_foreach@Base 1.0.0 + git_tag_free@Base 1.0.0 + git_tag_id@Base 1.0.0 + git_tag_list@Base 1.0.0 + git_tag_list_match@Base 1.0.0 + git_tag_lookup@Base 1.0.0 + git_tag_lookup_prefix@Base 1.0.0 + git_tag_message@Base 1.0.0 + git_tag_name@Base 1.0.0 + git_tag_owner@Base 1.0.0 + git_tag_peel@Base 1.0.0 + git_tag_tagger@Base 1.0.0 + git_tag_target@Base 1.0.0 + git_tag_target_id@Base 1.0.0 + git_tag_target_type@Base 1.0.0 + git_trace_set@Base 1.0.0 + git_transaction_commit@Base 1.0.0 + git_transaction_free@Base 1.0.0 + git_transaction_lock_ref@Base 1.0.0 + git_transaction_new@Base 1.0.0 + git_transaction_remove@Base 1.0.0 + git_transaction_set_reflog@Base 1.0.0 + git_transaction_set_symbolic_target@Base 1.0.0 + git_transaction_set_target@Base 1.0.0 + git_transport_init@Base 1.0.0 + git_transport_local@Base 1.0.0 + git_transport_new@Base 1.0.0 + git_transport_register@Base 1.0.0 + git_transport_smart@Base 1.0.0 + git_transport_smart_certificate_check@Base 1.0.0 + git_transport_smart_credentials@Base 1.0.0 + git_transport_smart_proxy_options@Base 1.0.0 + git_transport_ssh_with_paths@Base 1.0.0 + git_transport_unregister@Base 1.0.0 + git_tree_create_updated@Base 1.0.0 + git_tree_dup@Base 1.0.0 + git_tree_entry_byid@Base 1.0.0 + git_tree_entry_byindex@Base 1.0.0 + git_tree_entry_byname@Base 1.0.0 + git_tree_entry_bypath@Base 1.0.0 + git_tree_entry_cmp@Base 1.0.0 + git_tree_entry_dup@Base 1.0.0 + git_tree_entry_filemode@Base 1.0.0 + git_tree_entry_filemode_raw@Base 1.0.0 + git_tree_entry_free@Base 1.0.0 + git_tree_entry_id@Base 1.0.0 + git_tree_entry_name@Base 1.0.0 + git_tree_entry_to_object@Base 1.0.0 + git_tree_entry_type@Base 1.0.0 + git_tree_entrycount@Base 1.0.0 + git_tree_free@Base 1.0.0 + git_tree_id@Base 1.0.0 + git_tree_lookup@Base 1.0.0 + git_tree_lookup_prefix@Base 1.0.0 + git_tree_owner@Base 1.0.0 + git_tree_walk@Base 1.0.0 + git_treebuilder_clear@Base 1.0.0 + git_treebuilder_entrycount@Base 1.0.0 + git_treebuilder_filter@Base 1.0.0 + git_treebuilder_free@Base 1.0.0 + git_treebuilder_get@Base 1.0.0 + git_treebuilder_insert@Base 1.0.0 + git_treebuilder_new@Base 1.0.0 + git_treebuilder_remove@Base 1.0.0 + git_treebuilder_write@Base 1.0.0 + git_treebuilder_write_with_buffer@Base 1.0.0 + git_worktree_add@Base 1.0.0 + git_worktree_add_init_options@Base 1.0.0 + git_worktree_add_options_init@Base 1.0.0 + git_worktree_free@Base 1.0.0 + git_worktree_is_locked@Base 1.0.0 + git_worktree_is_prunable@Base 1.0.0 + git_worktree_list@Base 1.0.0 + git_worktree_lock@Base 1.0.0 + git_worktree_lookup@Base 1.0.0 + git_worktree_name@Base 1.0.0 + git_worktree_open_from_repository@Base 1.0.0 + git_worktree_path@Base 1.0.0 + git_worktree_prune@Base 1.0.0 + git_worktree_prune_init_options@Base 1.0.1 + git_worktree_prune_options_init@Base 1.0.0 + git_worktree_unlock@Base 1.0.0 + git_worktree_validate@Base 1.0.0 + giterr_clear@Base 1.0.0 + giterr_last@Base 1.0.0 + giterr_set_oom@Base 1.0.0 + giterr_set_str@Base 1.0.0 diff --git a/debian/libgit2-28.symbols b/debian/libgit2-28.symbols deleted file mode 100644 index 77c9b10a0..000000000 --- a/debian/libgit2-28.symbols +++ /dev/null @@ -1,834 +0,0 @@ -libgit2.so.28 libgit2-28 #MINVER# - git_annotated_commit_free@Base 0.26.0 - git_annotated_commit_from_fetchhead@Base 0.26.0 - git_annotated_commit_from_ref@Base 0.26.0 - git_annotated_commit_from_revspec@Base 0.26.0 - git_annotated_commit_id@Base 0.26.0 - git_annotated_commit_lookup@Base 0.26.0 - git_annotated_commit_ref@Base 0.28.1 - git_apply@Base 0.28.1 - git_apply_to_tree@Base 0.28.1 - git_attr_add_macro@Base 0.26.0 - git_attr_cache_flush@Base 0.26.0 - git_attr_foreach@Base 0.26.0 - git_attr_get@Base 0.26.0 - git_attr_get_many@Base 0.26.0 - git_attr_value@Base 0.26.0 - git_blame_buffer@Base 0.26.0 - git_blame_file@Base 0.26.0 - git_blame_free@Base 0.26.0 - git_blame_get_hunk_byindex@Base 0.26.0 - git_blame_get_hunk_byline@Base 0.26.0 - git_blame_get_hunk_count@Base 0.26.0 - git_blame_init_options@Base 0.26.0 - git_blob_create_frombuffer@Base 0.26.0 - git_blob_create_fromdisk@Base 0.26.0 - git_blob_create_fromstream@Base 0.26.0 - git_blob_create_fromstream_commit@Base 0.26.0 - git_blob_create_fromworkdir@Base 0.26.0 - git_blob_dup@Base 0.26.0 - git_blob_filtered_content@Base 0.26.0 - git_blob_free@Base 0.26.0 - git_blob_id@Base 0.26.0 - git_blob_is_binary@Base 0.26.0 - git_blob_lookup@Base 0.26.0 - git_blob_lookup_prefix@Base 0.26.0 - git_blob_owner@Base 0.26.0 - git_blob_rawcontent@Base 0.26.0 - git_blob_rawsize@Base 0.26.0 - git_branch_create@Base 0.26.0 - git_branch_create_from_annotated@Base 0.26.0 - git_branch_delete@Base 0.26.0 - git_branch_is_checked_out@Base 0.26.0 - git_branch_is_head@Base 0.26.0 - git_branch_iterator_free@Base 0.26.0 - git_branch_iterator_new@Base 0.26.0 - git_branch_lookup@Base 0.26.0 - git_branch_move@Base 0.26.0 - git_branch_name@Base 0.26.0 - git_branch_next@Base 0.26.0 - git_branch_remote_name@Base 0.26.0 - git_branch_set_upstream@Base 0.26.0 - git_branch_upstream@Base 0.26.0 - git_branch_upstream_name@Base 0.26.0 - git_branch_upstream_remote@Base 0.26.0 - git_buf_contains_nul@Base 0.26.0 - git_buf_dispose@Base 0.28.1 - git_buf_free@Base 0.26.0 - git_buf_grow@Base 0.26.0 - git_buf_is_binary@Base 0.26.0 - git_buf_set@Base 0.26.0 - git_checkout_head@Base 0.26.0 - git_checkout_index@Base 0.26.0 - git_checkout_init_options@Base 0.26.0 - git_checkout_tree@Base 0.26.0 - git_cherrypick@Base 0.26.0 - git_cherrypick_commit@Base 0.26.0 - git_cherrypick_init_options@Base 0.26.0 - git_clone@Base 0.26.0 - git_clone_init_options@Base 0.26.0 - git_commit_amend@Base 0.26.0 - git_commit_author@Base 0.26.0 - git_commit_author_with_mailmap@Base 0.28.1 - git_commit_body@Base 0.26.0 - git_commit_committer@Base 0.26.0 - git_commit_committer_with_mailmap@Base 0.28.1 - git_commit_create@Base 0.26.0 - git_commit_create_buffer@Base 0.26.0 - git_commit_create_from_callback@Base 0.26.0 - git_commit_create_from_ids@Base 0.26.0 - git_commit_create_v@Base 0.26.0 - git_commit_create_with_signature@Base 0.26.0 - git_commit_dup@Base 0.26.0 - git_commit_extract_signature@Base 0.26.0 - git_commit_free@Base 0.26.0 - git_commit_header_field@Base 0.26.0 - git_commit_id@Base 0.26.0 - git_commit_lookup@Base 0.26.0 - git_commit_lookup_prefix@Base 0.26.0 - git_commit_message@Base 0.26.0 - git_commit_message_encoding@Base 0.26.0 - git_commit_message_raw@Base 0.26.0 - git_commit_nth_gen_ancestor@Base 0.26.0 - git_commit_owner@Base 0.26.0 - git_commit_parent@Base 0.26.0 - git_commit_parent_id@Base 0.26.0 - git_commit_parentcount@Base 0.26.0 - git_commit_raw_header@Base 0.26.0 - git_commit_summary@Base 0.26.0 - git_commit_time@Base 0.26.0 - git_commit_time_offset@Base 0.26.0 - git_commit_tree@Base 0.26.0 - git_commit_tree_id@Base 0.26.0 - git_config_add_backend@Base 0.26.0 - git_config_add_file_ondisk@Base 0.26.0 - git_config_backend_foreach_match@Base 0.26.0 - git_config_delete_entry@Base 0.26.0 - git_config_delete_multivar@Base 0.26.0 - git_config_entry_free@Base 0.26.0 - git_config_find_global@Base 0.26.0 - git_config_find_programdata@Base 0.26.0 - git_config_find_system@Base 0.26.0 - git_config_find_xdg@Base 0.26.0 - git_config_foreach@Base 0.26.0 - git_config_foreach_match@Base 0.26.0 - git_config_free@Base 0.26.0 - git_config_get_bool@Base 0.26.0 - git_config_get_entry@Base 0.26.0 - git_config_get_int32@Base 0.26.0 - git_config_get_int64@Base 0.26.0 - git_config_get_mapped@Base 0.26.0 - git_config_get_multivar_foreach@Base 0.26.0 - git_config_get_path@Base 0.26.0 - git_config_get_string@Base 0.26.0 - git_config_get_string_buf@Base 0.26.0 - git_config_init_backend@Base 0.26.0 - git_config_iterator_free@Base 0.26.0 - git_config_iterator_glob_new@Base 0.26.0 - git_config_iterator_new@Base 0.26.0 - git_config_lock@Base 0.26.0 - git_config_lookup_map_value@Base 0.26.0 - git_config_multivar_iterator_new@Base 0.26.0 - git_config_new@Base 0.26.0 - git_config_next@Base 0.26.0 - git_config_open_default@Base 0.26.0 - git_config_open_global@Base 0.26.0 - git_config_open_level@Base 0.26.0 - git_config_open_ondisk@Base 0.26.0 - git_config_parse_bool@Base 0.26.0 - git_config_parse_int32@Base 0.26.0 - git_config_parse_int64@Base 0.26.0 - git_config_parse_path@Base 0.26.0 - git_config_set_bool@Base 0.26.0 - git_config_set_int32@Base 0.26.0 - git_config_set_int64@Base 0.26.0 - git_config_set_multivar@Base 0.26.0 - git_config_set_string@Base 0.26.0 - git_config_snapshot@Base 0.26.0 - git_config_unlock@Base 0.26.0 - git_cred_default_new@Base 0.26.0 - git_cred_free@Base 0.26.0 - git_cred_has_username@Base 0.26.0 - git_cred_ssh_custom_new@Base 0.26.0 - git_cred_ssh_interactive_new@Base 0.26.0 - git_cred_ssh_key_from_agent@Base 0.26.0 - git_cred_ssh_key_memory_new@Base 0.26.0 - git_cred_ssh_key_new@Base 0.26.0 - git_cred_username_new@Base 0.26.0 - git_cred_userpass@Base 0.26.0 - git_cred_userpass_plaintext_new@Base 0.26.0 - git_describe_commit@Base 0.26.0 - git_describe_format@Base 0.26.0 - git_describe_init_format_options@Base 0.26.0 - git_describe_init_options@Base 0.26.0 - git_describe_result_free@Base 0.26.0 - git_describe_workdir@Base 0.26.0 - git_diff_blob_to_buffer@Base 0.26.0 - git_diff_blobs@Base 0.26.0 - git_diff_buffers@Base 0.26.0 - git_diff_commit_as_email@Base 0.26.0 - git_diff_find_init_options@Base 0.26.0 - git_diff_find_similar@Base 0.26.0 - git_diff_foreach@Base 0.26.0 - git_diff_format_email@Base 0.26.0 - git_diff_format_email_init_options@Base 0.26.0 - git_diff_free@Base 0.26.0 - git_diff_from_buffer@Base 0.26.0 - git_diff_get_delta@Base 0.26.0 - git_diff_get_perfdata@Base 0.26.0 - git_diff_get_stats@Base 0.26.0 - git_diff_index_to_index@Base 0.26.0 - git_diff_index_to_workdir@Base 0.26.0 - git_diff_init_options@Base 0.26.0 - git_diff_is_sorted_icase@Base 0.26.0 - git_diff_merge@Base 0.26.0 - git_diff_num_deltas@Base 0.26.0 - git_diff_num_deltas_of_type@Base 0.26.0 - git_diff_patchid@Base 0.27.0 - git_diff_patchid_init_options@Base 0.27.0 - git_diff_print@Base 0.26.0 - git_diff_print_callback__to_buf@Base 0.26.0 - git_diff_print_callback__to_file_handle@Base 0.26.0 - git_diff_stats_deletions@Base 0.26.0 - git_diff_stats_files_changed@Base 0.26.0 - git_diff_stats_free@Base 0.26.0 - git_diff_stats_insertions@Base 0.26.0 - git_diff_stats_to_buf@Base 0.26.0 - git_diff_status_char@Base 0.26.0 - git_diff_to_buf@Base 0.26.0 - git_diff_tree_to_index@Base 0.26.0 - git_diff_tree_to_tree@Base 0.26.0 - git_diff_tree_to_workdir@Base 0.26.0 - git_diff_tree_to_workdir_with_index@Base 0.26.0 - git_error_clear@Base 0.28.1 - git_error_last@Base 0.28.1 - git_error_set_oom@Base 0.28.1 - git_error_set_str@Base 0.28.1 - git_fetch_init_options@Base 0.26.0 - git_filter_init@Base 0.26.0 - git_filter_list_apply_to_blob@Base 0.26.0 - git_filter_list_apply_to_data@Base 0.26.0 - git_filter_list_apply_to_file@Base 0.26.0 - git_filter_list_contains@Base 0.26.0 - git_filter_list_free@Base 0.26.0 - git_filter_list_length@Base 0.26.0 - git_filter_list_load@Base 0.26.0 - git_filter_list_new@Base 0.26.0 - git_filter_list_push@Base 0.26.0 - git_filter_list_stream_blob@Base 0.26.0 - git_filter_list_stream_data@Base 0.26.0 - git_filter_list_stream_file@Base 0.26.0 - git_filter_lookup@Base 0.26.0 - git_filter_register@Base 0.26.0 - git_filter_source_filemode@Base 0.26.0 - git_filter_source_flags@Base 0.26.0 - git_filter_source_id@Base 0.26.0 - git_filter_source_mode@Base 0.26.0 - git_filter_source_path@Base 0.26.0 - git_filter_source_repo@Base 0.26.0 - git_filter_unregister@Base 0.26.0 - git_graph_ahead_behind@Base 0.26.0 - git_graph_descendant_of@Base 0.26.0 - git_hashsig_compare@Base 0.26.0 - git_hashsig_create@Base 0.26.0 - git_hashsig_create_fromfile@Base 0.26.0 - git_hashsig_free@Base 0.26.0 - git_ignore_add_rule@Base 0.26.0 - git_ignore_clear_internal_rules@Base 0.26.0 - git_ignore_path_is_ignored@Base 0.26.0 - git_index_add@Base 0.26.0 - git_index_add_all@Base 0.26.0 - git_index_add_bypath@Base 0.26.0 - git_index_add_frombuffer@Base 0.26.0 - git_index_caps@Base 0.26.0 - git_index_checksum@Base 0.26.0 - git_index_clear@Base 0.26.0 - git_index_conflict_add@Base 0.26.0 - git_index_conflict_cleanup@Base 0.26.0 - git_index_conflict_get@Base 0.26.0 - git_index_conflict_iterator_free@Base 0.26.0 - git_index_conflict_iterator_new@Base 0.26.0 - git_index_conflict_next@Base 0.26.0 - git_index_conflict_remove@Base 0.26.0 - git_index_entry_is_conflict@Base 0.26.0 - git_index_entry_stage@Base 0.26.0 - git_index_entrycount@Base 0.26.0 - git_index_find@Base 0.26.0 - git_index_find_prefix@Base 0.26.0 - git_index_free@Base 0.26.0 - git_index_get_byindex@Base 0.26.0 - git_index_get_bypath@Base 0.26.0 - git_index_has_conflicts@Base 0.26.0 - git_index_iterator_free@Base 0.28.1 - git_index_iterator_new@Base 0.28.1 - git_index_iterator_next@Base 0.28.1 - git_index_name_add@Base 0.26.0 - git_index_name_clear@Base 0.26.0 - git_index_name_entrycount@Base 0.26.0 - git_index_name_get_byindex@Base 0.26.0 - git_index_new@Base 0.26.0 - git_index_open@Base 0.26.0 - git_index_owner@Base 0.26.0 - git_index_path@Base 0.26.0 - git_index_read@Base 0.26.0 - git_index_read_tree@Base 0.26.0 - git_index_remove@Base 0.26.0 - git_index_remove_all@Base 0.26.0 - git_index_remove_bypath@Base 0.26.0 - git_index_remove_directory@Base 0.26.0 - git_index_reuc_add@Base 0.26.0 - git_index_reuc_clear@Base 0.26.0 - git_index_reuc_entrycount@Base 0.26.0 - git_index_reuc_find@Base 0.26.0 - git_index_reuc_get_byindex@Base 0.26.0 - git_index_reuc_get_bypath@Base 0.26.0 - git_index_reuc_remove@Base 0.26.0 - git_index_set_caps@Base 0.26.0 - git_index_set_version@Base 0.26.0 - git_index_update_all@Base 0.26.0 - git_index_version@Base 0.26.0 - git_index_write@Base 0.26.0 - git_index_write_tree@Base 0.26.0 - git_index_write_tree_to@Base 0.26.0 - git_indexer_append@Base 0.26.0 - git_indexer_commit@Base 0.26.0 - git_indexer_free@Base 0.26.0 - git_indexer_hash@Base 0.26.0 - git_indexer_init_options@Base 0.28.1 - git_indexer_new@Base 0.26.0 - git_libgit2_features@Base 0.26.0 - git_libgit2_init@Base 0.26.0 - git_libgit2_opts@Base 0.26.0 - git_libgit2_shutdown@Base 0.26.0 - git_libgit2_version@Base 0.26.0 - git_mailmap_add_entry@Base 0.28.1 - git_mailmap_free@Base 0.28.1 - git_mailmap_from_buffer@Base 0.28.1 - git_mailmap_from_repository@Base 0.28.1 - git_mailmap_new@Base 0.28.1 - git_mailmap_resolve@Base 0.28.1 - git_mailmap_resolve_signature@Base 0.28.1 - git_mempack_dump@Base 0.27.0 - git_mempack_new@Base 0.27.0 - git_mempack_reset@Base 0.27.0 - git_merge@Base 0.26.0 - git_merge_analysis@Base 0.26.0 - git_merge_analysis_for_ref@Base 0.28.1 - git_merge_base@Base 0.26.0 - git_merge_base_many@Base 0.26.0 - git_merge_base_octopus@Base 0.26.0 - git_merge_bases@Base 0.26.0 - git_merge_bases_many@Base 0.26.0 - git_merge_commits@Base 0.26.0 - git_merge_driver_lookup@Base 0.26.0 - git_merge_driver_register@Base 0.26.0 - git_merge_driver_source_ancestor@Base 0.26.0 - git_merge_driver_source_file_options@Base 0.26.0 - git_merge_driver_source_ours@Base 0.26.0 - git_merge_driver_source_repo@Base 0.26.0 - git_merge_driver_source_theirs@Base 0.26.0 - git_merge_driver_unregister@Base 0.26.0 - git_merge_file@Base 0.26.0 - git_merge_file_from_index@Base 0.26.0 - git_merge_file_init_input@Base 0.26.0 - git_merge_file_init_options@Base 0.26.0 - git_merge_file_result_free@Base 0.26.0 - git_merge_init_options@Base 0.26.0 - git_merge_trees@Base 0.26.0 - git_message_prettify@Base 0.26.0 - git_message_trailer_array_free@Base 0.27.0 - git_message_trailers@Base 0.27.0 - git_note_author@Base 0.26.0 - git_note_commit_create@Base 0.27.0 - git_note_commit_iterator_new@Base 0.27.0 - git_note_commit_read@Base 0.27.0 - git_note_commit_remove@Base 0.27.0 - git_note_committer@Base 0.26.0 - git_note_create@Base 0.26.0 - git_note_default_ref@Base 0.26.0 - git_note_foreach@Base 0.26.0 - git_note_free@Base 0.26.0 - git_note_id@Base 0.26.0 - git_note_iterator_free@Base 0.26.0 - git_note_iterator_new@Base 0.26.0 - git_note_message@Base 0.26.0 - git_note_next@Base 0.26.0 - git_note_read@Base 0.26.0 - git_note_remove@Base 0.26.0 - git_object__size@Base 0.26.0 - git_object_dup@Base 0.26.0 - git_object_free@Base 0.26.0 - git_object_id@Base 0.26.0 - git_object_lookup@Base 0.26.0 - git_object_lookup_bypath@Base 0.26.0 - git_object_lookup_prefix@Base 0.26.0 - git_object_owner@Base 0.26.0 - git_object_peel@Base 0.26.0 - git_object_short_id@Base 0.26.0 - git_object_string2type@Base 0.26.0 - git_object_type2string@Base 0.26.0 - git_object_type@Base 0.26.0 - git_object_typeisloose@Base 0.26.0 - git_odb_add_alternate@Base 0.26.0 - git_odb_add_backend@Base 0.26.0 - git_odb_add_disk_alternate@Base 0.26.0 - git_odb_backend_loose@Base 0.26.0 - git_odb_backend_malloc@Base 0.26.0 - git_odb_backend_one_pack@Base 0.26.0 - git_odb_backend_pack@Base 0.26.0 - git_odb_exists@Base 0.26.0 - git_odb_exists_prefix@Base 0.26.0 - git_odb_expand_ids@Base 0.26.0 - git_odb_foreach@Base 0.26.0 - git_odb_free@Base 0.26.0 - git_odb_get_backend@Base 0.26.0 - git_odb_hash@Base 0.26.0 - git_odb_hashfile@Base 0.26.0 - git_odb_init_backend@Base 0.26.0 - git_odb_new@Base 0.26.0 - git_odb_num_backends@Base 0.26.0 - git_odb_object_data@Base 0.26.0 - git_odb_object_dup@Base 0.26.0 - git_odb_object_free@Base 0.26.0 - git_odb_object_id@Base 0.26.0 - git_odb_object_size@Base 0.26.0 - git_odb_object_type@Base 0.26.0 - git_odb_open@Base 0.26.0 - git_odb_open_rstream@Base 0.26.0 - git_odb_open_wstream@Base 0.26.0 - git_odb_read@Base 0.26.0 - git_odb_read_header@Base 0.26.0 - git_odb_read_prefix@Base 0.26.0 - git_odb_refresh@Base 0.26.0 - git_odb_stream_finalize_write@Base 0.26.0 - git_odb_stream_free@Base 0.26.0 - git_odb_stream_read@Base 0.26.0 - git_odb_stream_write@Base 0.26.0 - git_odb_write@Base 0.26.0 - git_odb_write_pack@Base 0.26.0 - git_oid_cmp@Base 0.26.0 - git_oid_cpy@Base 0.26.0 - git_oid_equal@Base 0.26.0 - git_oid_fmt@Base 0.26.0 - git_oid_fromraw@Base 0.26.0 - git_oid_fromstr@Base 0.26.0 - git_oid_fromstrn@Base 0.26.0 - git_oid_fromstrp@Base 0.26.0 - git_oid_iszero@Base 0.26.0 - git_oid_ncmp@Base 0.26.0 - git_oid_nfmt@Base 0.26.0 - git_oid_pathfmt@Base 0.26.0 - git_oid_shorten_add@Base 0.26.0 - git_oid_shorten_free@Base 0.26.0 - git_oid_shorten_new@Base 0.26.0 - git_oid_strcmp@Base 0.26.0 - git_oid_streq@Base 0.26.0 - git_oid_tostr@Base 0.26.0 - git_oid_tostr_s@Base 0.26.0 - git_oidarray_free@Base 0.26.0 - git_openssl_set_locking@Base 0.26.0 - git_packbuilder_foreach@Base 0.26.0 - git_packbuilder_free@Base 0.26.0 - git_packbuilder_hash@Base 0.26.0 - git_packbuilder_insert@Base 0.26.0 - git_packbuilder_insert_commit@Base 0.26.0 - git_packbuilder_insert_recur@Base 0.26.0 - git_packbuilder_insert_tree@Base 0.26.0 - git_packbuilder_insert_walk@Base 0.26.0 - git_packbuilder_new@Base 0.26.0 - git_packbuilder_object_count@Base 0.26.0 - git_packbuilder_set_callbacks@Base 0.26.0 - git_packbuilder_set_threads@Base 0.26.0 - git_packbuilder_write@Base 0.26.0 - git_packbuilder_write_buf@Base 0.26.0 - git_packbuilder_written@Base 0.26.0 - git_patch_free@Base 0.26.0 - git_patch_from_blob_and_buffer@Base 0.26.0 - git_patch_from_blobs@Base 0.26.0 - git_patch_from_buffers@Base 0.26.0 - git_patch_from_diff@Base 0.26.0 - git_patch_get_delta@Base 0.26.0 - git_patch_get_hunk@Base 0.26.0 - git_patch_get_line_in_hunk@Base 0.26.0 - git_patch_line_stats@Base 0.26.0 - git_patch_num_hunks@Base 0.26.0 - git_patch_num_lines_in_hunk@Base 0.26.0 - git_patch_print@Base 0.26.0 - git_patch_size@Base 0.26.0 - git_patch_to_buf@Base 0.26.0 - git_path_is_gitfile@Base 0.28.1 - git_pathspec_free@Base 0.26.0 - git_pathspec_match_diff@Base 0.26.0 - git_pathspec_match_index@Base 0.26.0 - git_pathspec_match_list_diff_entry@Base 0.26.0 - git_pathspec_match_list_entry@Base 0.26.0 - git_pathspec_match_list_entrycount@Base 0.26.0 - git_pathspec_match_list_failed_entry@Base 0.26.0 - git_pathspec_match_list_failed_entrycount@Base 0.26.0 - git_pathspec_match_list_free@Base 0.26.0 - git_pathspec_match_tree@Base 0.26.0 - git_pathspec_match_workdir@Base 0.26.0 - git_pathspec_matches_path@Base 0.26.0 - git_pathspec_new@Base 0.26.0 - git_proxy_init_options@Base 0.26.0 - git_push_init_options@Base 0.26.0 - git_rebase_abort@Base 0.26.0 - git_rebase_commit@Base 0.26.0 - git_rebase_finish@Base 0.26.0 - git_rebase_free@Base 0.26.0 - git_rebase_init@Base 0.26.0 - git_rebase_init_options@Base 0.26.0 - git_rebase_inmemory_index@Base 0.26.0 - git_rebase_next@Base 0.26.0 - git_rebase_open@Base 0.26.0 - git_rebase_operation_byindex@Base 0.26.0 - git_rebase_operation_current@Base 0.26.0 - git_rebase_operation_entrycount@Base 0.26.0 - git_refdb_backend_fs@Base 0.26.0 - git_refdb_compress@Base 0.26.0 - git_refdb_free@Base 0.26.0 - git_refdb_init_backend@Base 0.26.0 - git_refdb_new@Base 0.26.0 - git_refdb_open@Base 0.26.0 - git_refdb_set_backend@Base 0.26.0 - git_reference__alloc@Base 0.26.0 - git_reference__alloc_symbolic@Base 0.26.0 - git_reference_cmp@Base 0.26.0 - git_reference_create@Base 0.26.0 - git_reference_create_matching@Base 0.26.0 - git_reference_delete@Base 0.26.0 - git_reference_dup@Base 0.26.0 - git_reference_dwim@Base 0.26.0 - git_reference_ensure_log@Base 0.26.0 - git_reference_foreach@Base 0.26.0 - git_reference_foreach_glob@Base 0.26.0 - git_reference_foreach_name@Base 0.26.0 - git_reference_free@Base 0.26.0 - git_reference_has_log@Base 0.26.0 - git_reference_is_branch@Base 0.26.0 - git_reference_is_note@Base 0.26.0 - git_reference_is_remote@Base 0.26.0 - git_reference_is_tag@Base 0.26.0 - git_reference_is_valid_name@Base 0.26.0 - git_reference_iterator_free@Base 0.26.0 - git_reference_iterator_glob_new@Base 0.26.0 - git_reference_iterator_new@Base 0.26.0 - git_reference_list@Base 0.26.0 - git_reference_lookup@Base 0.26.0 - git_reference_name@Base 0.26.0 - git_reference_name_to_id@Base 0.26.0 - git_reference_next@Base 0.26.0 - git_reference_next_name@Base 0.26.0 - git_reference_normalize_name@Base 0.26.0 - git_reference_owner@Base 0.26.0 - git_reference_peel@Base 0.26.0 - git_reference_remove@Base 0.26.0 - git_reference_rename@Base 0.26.0 - git_reference_resolve@Base 0.26.0 - git_reference_set_target@Base 0.26.0 - git_reference_shorthand@Base 0.26.0 - git_reference_symbolic_create@Base 0.26.0 - git_reference_symbolic_create_matching@Base 0.26.0 - git_reference_symbolic_set_target@Base 0.26.0 - git_reference_symbolic_target@Base 0.26.0 - git_reference_target@Base 0.26.0 - git_reference_target_peel@Base 0.26.0 - git_reference_type@Base 0.26.0 - git_reflog_append@Base 0.26.0 - git_reflog_delete@Base 0.26.0 - git_reflog_drop@Base 0.26.0 - git_reflog_entry_byindex@Base 0.26.0 - git_reflog_entry_committer@Base 0.26.0 - git_reflog_entry_id_new@Base 0.26.0 - git_reflog_entry_id_old@Base 0.26.0 - git_reflog_entry_message@Base 0.26.0 - git_reflog_entrycount@Base 0.26.0 - git_reflog_free@Base 0.26.0 - git_reflog_read@Base 0.26.0 - git_reflog_rename@Base 0.26.0 - git_reflog_write@Base 0.26.0 - git_refspec_direction@Base 0.26.0 - git_refspec_dst@Base 0.26.0 - git_refspec_dst_matches@Base 0.26.0 - git_refspec_force@Base 0.26.0 - git_refspec_free@Base 0.28.1 - git_refspec_parse@Base 0.28.1 - git_refspec_rtransform@Base 0.26.0 - git_refspec_src@Base 0.26.0 - git_refspec_src_matches@Base 0.26.0 - git_refspec_string@Base 0.26.0 - git_refspec_transform@Base 0.26.0 - git_remote_add_fetch@Base 0.26.0 - git_remote_add_push@Base 0.26.0 - git_remote_autotag@Base 0.26.0 - git_remote_connect@Base 0.26.0 - git_remote_connected@Base 0.26.0 - git_remote_create@Base 0.26.0 - git_remote_create_anonymous@Base 0.26.0 - git_remote_create_detached@Base 0.27.0 - git_remote_create_init_options@Base 0.28.1 - git_remote_create_with_fetchspec@Base 0.26.0 - git_remote_create_with_opts@Base 0.28.1 - git_remote_default_branch@Base 0.26.0 - git_remote_delete@Base 0.26.0 - git_remote_disconnect@Base 0.26.0 - git_remote_download@Base 0.26.0 - git_remote_dup@Base 0.26.0 - git_remote_fetch@Base 0.26.0 - git_remote_free@Base 0.26.0 - git_remote_get_fetch_refspecs@Base 0.26.0 - git_remote_get_push_refspecs@Base 0.26.0 - git_remote_get_refspec@Base 0.26.0 - git_remote_init_callbacks@Base 0.26.0 - git_remote_is_valid_name@Base 0.26.0 - git_remote_list@Base 0.26.0 - git_remote_lookup@Base 0.26.0 - git_remote_ls@Base 0.26.0 - git_remote_name@Base 0.26.0 - git_remote_owner@Base 0.26.0 - git_remote_prune@Base 0.26.0 - git_remote_prune_refs@Base 0.26.0 - git_remote_push@Base 0.26.0 - git_remote_pushurl@Base 0.26.0 - git_remote_refspec_count@Base 0.26.0 - git_remote_rename@Base 0.26.0 - git_remote_set_autotag@Base 0.26.0 - git_remote_set_pushurl@Base 0.26.0 - git_remote_set_url@Base 0.26.0 - git_remote_stats@Base 0.26.0 - git_remote_stop@Base 0.26.0 - git_remote_update_tips@Base 0.26.0 - git_remote_upload@Base 0.26.0 - git_remote_url@Base 0.26.0 - git_repository__cleanup@Base 0.26.0 - git_repository_commondir@Base 0.26.0 - git_repository_config@Base 0.26.0 - git_repository_config_snapshot@Base 0.26.0 - git_repository_detach_head@Base 0.26.0 - git_repository_discover@Base 0.26.0 - git_repository_fetchhead_foreach@Base 0.26.0 - git_repository_free@Base 0.26.0 - git_repository_get_namespace@Base 0.26.0 - git_repository_hashfile@Base 0.26.0 - git_repository_head@Base 0.26.0 - git_repository_head_detached@Base 0.26.0 - git_repository_head_detached_for_worktree@Base 0.26.0 - git_repository_head_for_worktree@Base 0.26.0 - git_repository_head_unborn@Base 0.26.0 - git_repository_ident@Base 0.26.0 - git_repository_index@Base 0.26.0 - git_repository_init@Base 0.26.0 - git_repository_init_ext@Base 0.26.0 - git_repository_init_init_options@Base 0.26.0 - git_repository_is_bare@Base 0.26.0 - git_repository_is_empty@Base 0.26.0 - git_repository_is_shallow@Base 0.26.0 - git_repository_is_worktree@Base 0.26.0 - git_repository_item_path@Base 0.26.0 - git_repository_mergehead_foreach@Base 0.26.0 - git_repository_message@Base 0.26.0 - git_repository_message_remove@Base 0.26.0 - git_repository_new@Base 0.26.0 - git_repository_odb@Base 0.26.0 - git_repository_open@Base 0.26.0 - git_repository_open_bare@Base 0.26.0 - git_repository_open_ext@Base 0.26.0 - git_repository_open_from_worktree@Base 0.26.0 - git_repository_path@Base 0.26.0 - git_repository_refdb@Base 0.26.0 - git_repository_reinit_filesystem@Base 0.26.0 - git_repository_set_bare@Base 0.26.0 - git_repository_set_config@Base 0.26.0 - git_repository_set_head@Base 0.26.0 - git_repository_set_head_detached@Base 0.26.0 - git_repository_set_head_detached_from_annotated@Base 0.26.0 - git_repository_set_ident@Base 0.26.0 - git_repository_set_index@Base 0.26.0 - git_repository_set_namespace@Base 0.26.0 - git_repository_set_odb@Base 0.26.0 - git_repository_set_refdb@Base 0.26.0 - git_repository_set_workdir@Base 0.26.0 - git_repository_state@Base 0.26.0 - git_repository_state_cleanup@Base 0.26.0 - git_repository_submodule_cache_all@Base 0.26.0 - git_repository_submodule_cache_clear@Base 0.26.0 - git_repository_workdir@Base 0.26.0 - git_repository_wrap_odb@Base 0.26.0 - git_reset@Base 0.26.0 - git_reset_default@Base 0.26.0 - git_reset_from_annotated@Base 0.26.0 - git_revert@Base 0.26.0 - git_revert_commit@Base 0.26.0 - git_revert_init_options@Base 0.26.0 - git_revparse@Base 0.26.0 - git_revparse_ext@Base 0.26.0 - git_revparse_single@Base 0.26.0 - git_revwalk_add_hide_cb@Base 0.26.0 - git_revwalk_free@Base 0.26.0 - git_revwalk_hide@Base 0.26.0 - git_revwalk_hide_glob@Base 0.26.0 - git_revwalk_hide_head@Base 0.26.0 - git_revwalk_hide_ref@Base 0.26.0 - git_revwalk_new@Base 0.26.0 - git_revwalk_next@Base 0.26.0 - git_revwalk_push@Base 0.26.0 - git_revwalk_push_glob@Base 0.26.0 - git_revwalk_push_head@Base 0.26.0 - git_revwalk_push_range@Base 0.26.0 - git_revwalk_push_ref@Base 0.26.0 - git_revwalk_repository@Base 0.26.0 - git_revwalk_reset@Base 0.26.0 - git_revwalk_simplify_first_parent@Base 0.26.0 - git_revwalk_sorting@Base 0.26.0 - git_signature_default@Base 0.26.0 - git_signature_dup@Base 0.26.0 - git_signature_free@Base 0.26.0 - git_signature_from_buffer@Base 0.26.0 - git_signature_new@Base 0.26.0 - git_signature_now@Base 0.26.0 - git_smart_subtransport_git@Base 0.26.0 - git_smart_subtransport_http@Base 0.26.0 - git_smart_subtransport_ssh@Base 0.26.0 - git_stash_apply@Base 0.26.0 - git_stash_apply_init_options@Base 0.26.0 - git_stash_drop@Base 0.26.0 - git_stash_foreach@Base 0.26.0 - git_stash_pop@Base 0.26.0 - git_stash_save@Base 0.26.0 - git_status_byindex@Base 0.26.0 - git_status_file@Base 0.26.0 - git_status_foreach@Base 0.26.0 - git_status_foreach_ext@Base 0.26.0 - git_status_init_options@Base 0.26.0 - git_status_list_entrycount@Base 0.26.0 - git_status_list_free@Base 0.26.0 - git_status_list_get_perfdata@Base 0.26.0 - git_status_list_new@Base 0.26.0 - git_status_should_ignore@Base 0.26.0 - git_strarray_copy@Base 0.26.0 - git_strarray_free@Base 0.26.0 - git_stream_register@Base 0.28.1 - git_stream_register_tls@Base 0.26.0 - git_submodule_add_finalize@Base 0.26.0 - git_submodule_add_setup@Base 0.26.0 - git_submodule_add_to_index@Base 0.26.0 - git_submodule_branch@Base 0.26.0 - git_submodule_fetch_recurse_submodules@Base 0.26.0 - git_submodule_foreach@Base 0.26.0 - git_submodule_free@Base 0.26.0 - git_submodule_head_id@Base 0.26.0 - git_submodule_ignore@Base 0.26.0 - git_submodule_index_id@Base 0.26.0 - git_submodule_init@Base 0.26.0 - git_submodule_location@Base 0.26.0 - git_submodule_lookup@Base 0.26.0 - git_submodule_name@Base 0.26.0 - git_submodule_open@Base 0.26.0 - git_submodule_owner@Base 0.26.0 - git_submodule_path@Base 0.26.0 - git_submodule_reload@Base 0.26.0 - git_submodule_repo_init@Base 0.26.0 - git_submodule_resolve_url@Base 0.26.0 - git_submodule_set_branch@Base 0.26.0 - git_submodule_set_fetch_recurse_submodules@Base 0.26.0 - git_submodule_set_ignore@Base 0.26.0 - git_submodule_set_update@Base 0.26.0 - git_submodule_set_url@Base 0.26.0 - git_submodule_status@Base 0.26.0 - git_submodule_sync@Base 0.26.0 - git_submodule_update@Base 0.26.0 - git_submodule_update_init_options@Base 0.26.0 - git_submodule_update_strategy@Base 0.26.0 - git_submodule_url@Base 0.26.0 - git_submodule_wd_id@Base 0.26.0 - git_tag_annotation_create@Base 0.26.0 - git_tag_create@Base 0.26.0 - git_tag_create_frombuffer@Base 0.26.0 - git_tag_create_lightweight@Base 0.26.0 - git_tag_delete@Base 0.26.0 - git_tag_dup@Base 0.26.0 - git_tag_foreach@Base 0.26.0 - git_tag_free@Base 0.26.0 - git_tag_id@Base 0.26.0 - git_tag_list@Base 0.26.0 - git_tag_list_match@Base 0.26.0 - git_tag_lookup@Base 0.26.0 - git_tag_lookup_prefix@Base 0.26.0 - git_tag_message@Base 0.26.0 - git_tag_name@Base 0.26.0 - git_tag_owner@Base 0.26.0 - git_tag_peel@Base 0.26.0 - git_tag_tagger@Base 0.26.0 - git_tag_target@Base 0.26.0 - git_tag_target_id@Base 0.26.0 - git_tag_target_type@Base 0.26.0 - git_trace_set@Base 0.26.0 - git_transaction_commit@Base 0.26.0 - git_transaction_free@Base 0.26.0 - git_transaction_lock_ref@Base 0.26.0 - git_transaction_new@Base 0.26.0 - git_transaction_remove@Base 0.26.0 - git_transaction_set_reflog@Base 0.26.0 - git_transaction_set_symbolic_target@Base 0.26.0 - git_transaction_set_target@Base 0.26.0 - git_transport_init@Base 0.26.0 - git_transport_local@Base 0.26.0 - git_transport_new@Base 0.26.0 - git_transport_register@Base 0.26.0 - git_transport_smart@Base 0.26.0 - git_transport_smart_certificate_check@Base 0.26.0 - git_transport_smart_credentials@Base 0.26.0 - git_transport_smart_proxy_options@Base 0.26.0 - git_transport_ssh_with_paths@Base 0.26.0 - git_transport_unregister@Base 0.26.0 - git_tree_create_updated@Base 0.26.0 - git_tree_dup@Base 0.26.0 - git_tree_entry_byid@Base 0.26.0 - git_tree_entry_byindex@Base 0.26.0 - git_tree_entry_byname@Base 0.26.0 - git_tree_entry_bypath@Base 0.26.0 - git_tree_entry_cmp@Base 0.26.0 - git_tree_entry_dup@Base 0.26.0 - git_tree_entry_filemode@Base 0.26.0 - git_tree_entry_filemode_raw@Base 0.26.0 - git_tree_entry_free@Base 0.26.0 - git_tree_entry_id@Base 0.26.0 - git_tree_entry_name@Base 0.26.0 - git_tree_entry_to_object@Base 0.26.0 - git_tree_entry_type@Base 0.26.0 - git_tree_entrycount@Base 0.26.0 - git_tree_free@Base 0.26.0 - git_tree_id@Base 0.26.0 - git_tree_lookup@Base 0.26.0 - git_tree_lookup_prefix@Base 0.26.0 - git_tree_owner@Base 0.26.0 - git_tree_walk@Base 0.26.0 - git_treebuilder_clear@Base 0.26.0 - git_treebuilder_entrycount@Base 0.26.0 - git_treebuilder_filter@Base 0.26.0 - git_treebuilder_free@Base 0.26.0 - git_treebuilder_get@Base 0.26.0 - git_treebuilder_insert@Base 0.26.0 - git_treebuilder_new@Base 0.26.0 - git_treebuilder_remove@Base 0.26.0 - git_treebuilder_write@Base 0.26.0 - git_treebuilder_write_with_buffer@Base 0.26.0 - git_worktree_add@Base 0.26.0 - git_worktree_add_init_options@Base 0.28.1 - git_worktree_free@Base 0.26.0 - git_worktree_is_locked@Base 0.26.0 - git_worktree_is_prunable@Base 0.26.0 - git_worktree_list@Base 0.26.0 - git_worktree_lock@Base 0.26.0 - git_worktree_lookup@Base 0.26.0 - git_worktree_name@Base 0.28.1 - git_worktree_open_from_repository@Base 0.26.0 - git_worktree_path@Base 0.28.1 - git_worktree_prune@Base 0.26.0 - git_worktree_prune_init_options@Base 0.26.0 - git_worktree_unlock@Base 0.26.0 - git_worktree_validate@Base 0.26.0 - giterr_clear@Base 0.26.0 - giterr_last@Base 0.26.0 - giterr_set_oom@Base 0.26.0 - giterr_set_str@Base 0.26.0 diff --git a/debian/patches/disable-online-tests.patch b/debian/patches/disable-online-tests.patch index 592720aa7..c513b58cc 100644 --- a/debian/patches/disable-online-tests.patch +++ b/debian/patches/disable-online-tests.patch @@ -2,13 +2,15 @@ Skip tests that needs an active internet connection --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt -@@ -57,9 +57,4 @@ IF (MSVC_IDE) - SET_SOURCE_FILES_PROPERTIES("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h") - ENDIF () +@@ -60,11 +60,4 @@ + ENDIF() + ENDFUNCTION(ADD_CLAR_TEST) --ADD_TEST(offline "${libgit2_BINARY_DIR}/libgit2_clar" -v -xonline) - ADD_TEST(invasive "${libgit2_BINARY_DIR}/libgit2_clar" -v -score::ftruncate -sfilter::stream::bigfile -sodb::largefiles -siterator::workdir::filesystem_gunk -srepo::init -srepo::init::at_filesystem_root) --ADD_TEST(online "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline) --ADD_TEST(gitdaemon "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::push) --ADD_TEST(ssh "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths) --ADD_TEST(proxy "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::clone::proxy_credentials_in_url -sonline::clone::proxy_credentials_request) +-ADD_CLAR_TEST(offline -v -xonline) + ADD_CLAR_TEST(invasive -v -score::ftruncate -sfilter::stream::bigfile -sodb::largefiles -siterator::workdir::filesystem_gunk -srepo::init -srepo::init::at_filesystem_root) +-ADD_CLAR_TEST(online -v -sonline) +-ADD_CLAR_TEST(gitdaemon -v -sonline::push) +-ADD_CLAR_TEST(ssh -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths -sonline::clone::path_whitespace_ssh) +-ADD_CLAR_TEST(proxy -v -sonline::clone::proxy) +-ADD_CLAR_TEST(auth_clone -v -sonline::clone::cred) +-ADD_CLAR_TEST(auth_clone_and_push -v -sonline::clone::push -sonline::push) diff --git a/debian/patches/enable-repro-builds.patch b/debian/patches/enable-repro-builds.patch new file mode 100644 index 000000000..8f6370f51 --- /dev/null +++ b/debian/patches/enable-repro-builds.patch @@ -0,0 +1,19 @@ +Description: Enable reproducible builds by default. +Author: Utkarsh Gupta + +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -199,6 +199,13 @@ + SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") + SET(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}") + SET(CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL}") ++ ++ # Enable reproducible builds by default ++ SET(CMAKE_C_FLAGS "-ffile-prefix-map=${CMAKE_SOURCE_DIR}=. ${CMAKE_C_FLAGS}") ++ SET(CMAKE_C_ARCHIVE_CREATE " Dqc ") ++ SET(CMAKE_C_ARCHIVE_APPEND " Dq ") ++ SET(CMAKE_C_ARCHIVE_FINISH " -D ") ++ + ELSE () + IF (ENABLE_REPRODUCIBLE_BUILDS) + SET(CMAKE_C_ARCHIVE_CREATE " Dqc ") diff --git a/debian/patches/reprotest.patch b/debian/patches/reprotest.patch new file mode 100644 index 000000000..5457f6954 --- /dev/null +++ b/debian/patches/reprotest.patch @@ -0,0 +1,95 @@ +From b85eefb4604d3ca6983bf80e7dc9d1cedde072fd Mon Sep 17 00:00:00 2001 +From: Patrick Steinhardt +Date: Fri, 15 May 2020 19:52:40 +0200 +Subject: [PATCH] cmake: Sort source files for reproducible builds + +We currently use `FILE(GLOB ...)` in most places to find source and +header files. This is problematic in that the order of files returned +depends on the operating system's directory iteration order and may thus +not be deterministic. As a result, we link object files in unspecified +order, which may cause the linker to emit different code across runs. + +Fix this issue by sorting all code used as input to the libgit2 library +to improve the reliability of reproducible builds. + +--- a/cmake/Modules/SelectHashes.cmake ++++ b/cmake/Modules/SelectHashes.cmake +@@ -66,4 +66,6 @@ + MESSAGE(FATAL_ERROR "Asked for unknown SHA1 backend: ${SHA1_BACKEND}") + ENDIF() + ++list(SORT SRC_SHA1) ++ + ADD_FEATURE_INFO(SHA ON "using ${SHA1_BACKEND}") +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -76,12 +76,13 @@ + ADD_FEATURE_INFO(threadsafe THREADSAFE "threadsafe support") + + +-IF (WIN32 AND EMBED_SSH_PATH) +- FILE(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") +- LIST(APPEND LIBGIT2_SYSTEM_INCLUDES "${EMBED_SSH_PATH}/include") +- FILE(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") +- SET(GIT_SSH 1) +-ENDIF() ++if(WIN32 AND EMBED_SSH_PATH) ++ file(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") ++ list(SORT SRC_SSH) ++ list(APPEND LIBGIT2_SYSTEM_INCLUDES "${EMBED_SSH_PATH}/include") ++ file(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") ++ set(GIT_SSH 1) ++endif() + + IF (WIN32 AND WINHTTP) + SET(GIT_WINHTTP 1) +@@ -267,33 +268,38 @@ + ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64) + + # Collect sourcefiles +-FILE(GLOB SRC_H ++file(GLOB SRC_H + "${libgit2_SOURCE_DIR}/include/git2.h" + "${libgit2_SOURCE_DIR}/include/git2/*.h" + "${libgit2_SOURCE_DIR}/include/git2/sys/*.h") ++list(SORT SRC_H) + + # On Windows use specific platform sources +-IF (WIN32 AND NOT CYGWIN) +- IF(MSVC) ++if (WIN32 AND NOT CYGWIN) ++ if(MSVC) + SET(WIN_RC "win32/git2.rc") +- ENDIF() ++ endif() + +- FILE(GLOB SRC_OS win32/*.c win32/*.h) +-ELSEIF (AMIGA) +- ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R -DNO_MMAP) +-ELSE() +- FILE(GLOB SRC_OS unix/*.c unix/*.h) +-ENDIF() ++ file(GLOB SRC_OS win32/*.c win32/*.h) ++ list(SORT SRC_OS) ++elseif(AMIGA) ++ add_definitions(-DNO_ADDRINFO -DNO_READDIR_R -DNO_MMAP) ++else() ++ file(GLOB SRC_OS unix/*.c unix/*.h) ++ list(SORT SRC_OS) ++endif() + + IF (USE_LEAK_CHECKER STREQUAL "valgrind") + ADD_DEFINITIONS(-DVALGRIND) + ENDIF() + +-FILE(GLOB SRC_GIT2 *.c *.h ++file(GLOB SRC_GIT2 *.c *.h + allocators/*.c allocators/*.h + streams/*.c streams/*.h + transports/*.c transports/*.h + xdiff/*.c xdiff/*.h) ++list(SORT SRC_GIT2) ++ + IF(APPLE) + # The old Secure Transport API has been deprecated in macOS 10.15. + SET_SOURCE_FILES_PROPERTIES(streams/stransport.c PROPERTIES COMPILE_FLAGS -Wno-deprecated) diff --git a/debian/patches/series b/debian/patches/series index 08c699a18..82d0e1ff6 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1 +1,3 @@ disable-online-tests.patch +reprotest.patch +enable-repro-builds.patch diff --git a/debian/rules b/debian/rules index 15ebf2f4a..66e8d0551 100755 --- a/debian/rules +++ b/debian/rules @@ -24,6 +24,9 @@ override_dh_auto_configure: -DUSE_GSSAPI:BOOL=ON \ -DTHREADSAFE:BOOL=ON \ -DBUILD_CLAR:BOOL=ON \ + -DUSE_NTLMCLIENT=OFF \ + -DENABLE_REPRODUCIBLE_BUILDS=ON \ + -DUSE_HTTP_PARSER=system \ -DLIB_INSTALL_DIR:STRING=lib/$(DEB_HOST_MULTIARCH) dh_auto_configure --builddirectory=build-debian-devel -- \ @@ -35,6 +38,9 @@ override_dh_auto_configure: -DUSE_GSSAPI:BOOL=ON \ -DBUILD_CLAR:BOOL=OFF \ -DBUILD_SHARED_LIBS:BOOL=OFF \ + -DUSE_NTLMCLIENT=OFF \ + -DENABLE_REPRODUCIBLE_BUILDS=ON \ + -DUSE_HTTP_PARSER=system \ -DLIB_INSTALL_DIR:STRING=lib/$(DEB_HOST_MULTIARCH) @@ -55,9 +61,6 @@ override_dh_auto_test: mkdir -p build-debian-devel/tmp dh_auto_test --builddirectory=build-debian-devel -override_dh_strip: - dh_strip --dbgsym-migration='libgit2-dbg (<< 0.26.0+dfsg.1-1~)' - override_dh_installexamples: dh_installexamples --exclude .gitignore diff --git a/debian/upstream/metadata b/debian/upstream/metadata new file mode 100644 index 000000000..6c3a40520 --- /dev/null +++ b/debian/upstream/metadata @@ -0,0 +1,5 @@ +--- +Bug-Database: https://github.com/libgit2/libgit2/issues +Bug-Submit: https://github.com/libgit2/libgit2/issues/new +Repository: https://github.com/libgit2/libgit2.git +Repository-Browse: https://github.com/libgit2/libgit2 diff --git a/docs/changelog.md b/docs/changelog.md index 80ff16b44..c2423c338 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,7 +1,372 @@ -v0.28.4 --------- +v1.0.1 +------ -This is a security release fixing the following issues: +This is a bugfix release with the following changes: + +- Calculating information about renamed files during merges is more + efficient because dissimilarity about files is now being cached and + no longer needs to be recomputed. + +- The `git_worktree_prune_init_options` has been correctly restored for + backward compatibility. In v1.0 it was incorrectly deprecated with a + typo. + +- The optional ntlmclient dependency now supports NetBSD. + +- A bug where attempting to stash on a bare repository may have failed + has been fixed. + +- Configuration files that are unreadable due to permissions are now + silently ignored, and treated as if they do not exist. This matches + git's behavior; previously this case would have been an error. + +- v4 index files are now correctly written; previously we would read + them correctly but would not write the prefix-compression accurately, + causing corruption. + +- A bug where the smart HTTP transport could not read large data packets + has been fixed. Previously, fetching from servers like Gerrit, that + sent large data packets, would error. + +v1.0 +---- + +This is release v1.0 "Luftschloss", which is the first stabe release of +libgit2. The API will stay compatible across all releases of the same major +version. This release includes bugfixes only and supersedes v0.99, which will +stop being maintained. Both v0.27 and v0.28 stay supported in accordance with +our release policy. + +### Changes or improvements + +- CMake was converted to make use of the GNUInstallDirs module for both our + pkgconfig and install targets in favor of our custom build options + `BIN_INSTALL_DIR`, `LIB_INSTALL_DIR` and `INCLUDE_INSTALL_DIR`. Instead, you + can now use CMakes standard variables `CMAKE_INSTALL_BINDIR`, + `CMAKE_INSTALL_LIBDIR` and `CMAKE_INSTALL_INCLUDEDIR`. + +- Some CMake build options accepted either a specific value or a boolean value + to disable the option altogether or use automatic detection. We only accepted + "ON" or "OFF", but none of the other values CMake recognizes as boolean. This + was aligned with CMake's understanding of booleans. + +- The installed pkgconfig file contained incorrect values for both `libdir` and + `includedir` variables. + +- If using pcre2 for regular expressions, then we incorrectly added "pcre2" + instead of "pcre2-8" to our pkgconfig dependencies, which was corrected. + +- Fixed building the bundled ntlmclient dependency on FreeBSD, OpenBSD and + SunOS. + +- When writing symlinks on Windows, we incorrectly handled relative symlink + targets, which was corrected. + +- When using the HTTP protocol via macOS' SecureTransport implementation, reads + could stall at the end of the session and only continue after a timeout of 60 + seconds was reached. + +- The filesystem-based reference callback didn't corectly initialize the backend + version. + +- A segmentation fault was fixed when calling `git_blame_buffer()` for files + that were modified and added to the index. + +- A backwards-incompatible change was introduced when we moved some structures + from "git2/credentials.h" into "git2/sys/credentials.h". This was fixed in the + case where you do not use hard deprecation. + +- Improved error handling in various places. + + +v0.99 +----- + +This is v0.99 "Torschlusspanik". This will be the last minor release +before libgit2 v1.0. We expect to only respond to bugs in this release, +to stabilize it for next major release. + +It contains significant refactorings, but is expected to be API-compatible +with v0.28.0. + +### Changes or improvements + +* When fetching from an anonymous remote using a URL with authentication + information provided in the URL (eg `https://foo:bar@example.com/repo`), + we would erroneously include the literal URL in the FETCH_HEAD file. + We now remove that to match git's behavior. + +* Some credential structures, enums and values have been renamed: + `git_cred` is now `git_credential`. `git_credtype_t` is now + `git_credential_t`. Functions and types beginning with + `git_cred_` now begin with `git_credential`, and constants beginning + with `GIT_CREDTYPE` now begin with `GIT_CREDENTIAL`. The former names + are deprecated. + +* Several function signatures have been changed to return an `int` to + indicate error conditions. We encourage you to check them for errors + in the standard way. + + * `git_attr_cache_flush` + * `git_error_set_str` + * `git_index_name_clear` + * `git_index_reuc_clear` + * `git_libgit2_version` + * `git_mempack_reset` + * `git_oid_cpy` + * `git_oid_fmt` + * `git_oid_fromraw` + * `git_oid_nfmt` + * `git_oid_pathfmt` + * `git_remote_stop` + * `git_remote_disconnect` + * `git_repository__cleanup` + * `git_repository_set_config` + * `git_repository_set_index` + * `git_repository_set_odb` + * `git_repository_set_refdb` + * `git_revwalk_reset` + * `git_revwalk_simplify_first_parent` + * `git_revwalk_sorting` + * `git_treebuilder_clear` + * `git_treebuilder_filter` + +* The NTLM and Negotiate authentication mechanisms are now supported when + talking to git implementations hosted on Apache or nginx servers. + +* The `HEAD` symbolic reference can no longer be deleted. + +* `git_merge_driver_source_repo` no longer returns a `const git_repository *`, + it now returns a non-`const` `git_repository *`. + +* Relative symbolic links are now supported on Windows when `core.symlinks` + is enabled. + +* Servers that provide query parameters with a redirect are now supported. + +* `git_submodule_sync` will now resolve relative URLs. + +* When creating git endpoint URLs, double-slashes are no longer used when + the given git URL has a trailing slash. + +* On Windows, a `DllMain` function is no longer included and thread-local + storage has moved to fiber-local storage in order to prevent race + conditions during shutdown. + +* The tracing mechanism (`GIT_TRACE`) is now enabled by default and does + not need to be explicitly enabled in CMake. + +* The size of Git objects is now represented by `git_object_size_t` + instead of `off_t`. + +* Binary patches without data can now be parsed. + +* A configuration snapshot can now be created from another configuration + snapshot, not just a "true" configuration object. + +* The `git_commit_with_signature` API will now ensure that referenced + objects exist in the object database. + +* Stash messages containing newlines will now be replaced with spaces; + they will no longer be (erroneously) written to the repository. + +* `git_commit_create_with_signature` now verifies the commit information + to ensure that it points to a valid tree and valid parents. + +* `git_apply` has an option `GIT_APPLY_CHECK` that will only do a dry-run. + The index and working directory will remain unmodified, and application + will report if it would have worked. + +* Patches produced by Mercurial (those that lack some git extended headers) + can now be parsed and applied. + +* Reference locks are obeyed correctly on POSIX platforms, instead of + being removed. + +* Patches with empty new files can now be read and applied. + +* `git_apply_to_tree` can now correctly apply patches that add new files. + +* The program data configuration on Windows (`C:\ProgramData\Git\config`) + must be owned by an administrator, a system account or the current user + to be read. + +* `git_blob_filtered_content` is now deprecated in favor of `git_blob_filter`. + +* Configuration files can now be included conditionally using the + `onbranch` conditional. + +* Checkout can now properly create and remove symbolic links to directories + on Windows. + +* Stash no longer recomputes trees when committing a worktree, for + improved performance. + +* Repository templates can now include a `HEAD` file to default the + initial default branch. + +* Some configuration structures, enums and values have been renamed: + `git_cvar_map` is now `git_configmap`, `git_cvar_t` is now + `git_configmap_t`, `GIT_CVAR_FALSE` is now `GIT_CONFIGMAP_FALSE`, + `GIT_CVAR_TRUE` is now `GIT_CONFIGMAP_TRUE`, `GIT_CVAR_INT32` is now + `GIT_CONFIGMAP_INT32`, and `GIT_CVAR_STRING` is now `GIT_CONFIGMAP_STRING`. + The former names are deprecated. + +* Repositories can now be created at the root of a Windows drive. + +* Configuration lookups are now more efficiently cached. + +* `git_commit_create_with_signature` now supports a `NULL` signature, + which will create a commit without adding a signature. + +* When a repository lacks an `info` "common directory", we will no + longer erroneously return `GIT_ENOTFOUND` for all attribute lookups. + +* Several attribute macros have been renamed: `GIT_ATTR_TRUE` is now + `GIT_ATTR_IS_TRUE`, `GIT_ATTR_FALSE` is now `GIT_ATTR_IS_FALSE`, + `GIT_ATTR_UNSPECIFIED` is now `GIT_ATTR_IS_UNSPECIFIED`. The + attribute enum `git_attr_t` is now `git_attr_value_t` and its + values have been renamed: `GIT_ATTR_UNSPECIFIED_T` is now + `GIT_ATTR_VALUE_UNSPECIFIED`, `GIT_ATTR_TRUE_T` is now + `GIT_ATTR_VALUE_TRUE`, `GIT_ATTR_FALSE_T` is now `GIT_ATTR_VALUE_FALSE`, + and `GIT_ATTR_VALUE_T` is now `GIT_ATTR_VALUE_STRING`. The + former names are deprecated. + +* `git_object__size` is now `git_object_size`. The former name is + deprecated. + +* `git_tag_create_frombuffer` is now `git_tag_create_from_buffer`. The + former name is deprecated. + +* Several blob creation functions have been renamed: + `git_blob_create_frombuffer` is now named `git_blob_create_from_buffer`, + `git_blob_create_fromdisk` is now named `git_blob_create_from_disk`, + `git_blob_create_fromworkdir` is now named `git_blob_create_from_workdir`, + `git_blob_create_fromstream` is now named `git_blob_create_from_stream`, + and `git_blob_create_fromstream_commit` is now named + `git_blob_create_from_stream_commit`. The former names are deprecated. + +* The function `git_oid_iszero` is now named `git_oid_is_zero`. The + former name is deprecated. + +* Pattern matching is now done using `wildmatch` instead of `fnmatch` + for compatibility with git. + +* The option initialization functions suffixed by `init_options` are now + suffixed with `options_init`. (For example, `git_checkout_init_options` + is now `git_checkout_options_init`.) The former names are deprecated. + +* NTLM2 authentication is now supported on non-Windows platforms. + +* The `git_cred_sign_callback` callback is now named `git_cred_sign_cb`. + The `git_cred_ssh_interactive_callback` callback is now named + `git_cred_ssh_interactive_cb`. + +* Ignore files now: + + * honor escaped trailing whitespace. + * do not incorrectly negate sibling paths of a negated pattern. + * honor rules that stop ignoring files after a wildcard + +* Attribute files now: + + * honor leading and trailing whitespace. + * treat paths beginning with `\` as absolute only on Windows. + * properly handle escaped characters. + * stop reading macros defined in subdirectories + +* The C locale is now correctly used when parsing regular expressions. + +* The system PCRE2 or PCRE regular expression libraries are now used + when `regcomp_l` is not available on the system. If none of these + are available on the system, an included version of PCRE is used. + +* Wildcards in reference specifications are now supported beyond simply + a bare wildcard (`*`) for compatibility with git. + +* When `git_ignore_path_is_ignored` is provided a path with a trailing + slash (eg, `dir/`), it will now treat it as a directory for the + purposes of ignore matching. + +* Patches that add or remove a file with a space in the path can now + be correctly parsed. + +* The `git_remote_completion_type` type is now `git_remote_completion_t`. + The former name is deprecated. + +* The `git_odb_backend_malloc` is now `git_odb_backend_data_alloc`. The + former name is deprecated. + +* The `git_transfer_progress_cb` callback is now `git_indexer_progress_cb` + and the `git_transfer_progress` structure is now `git_indexer_progress`. + The former names are deprecated. + +* The example projects are now contained in a single `lg2` executable + for ease of use. + +* libgit2 now correctly handles more URLs, such as + `http://example.com:/repo.git` (colon but no port), + `http://example.com` (no path), + and `http://example.com:8080/` (path is /, nonstandard port). + +* A carefully constructed commit object with a very large number + of parents may lead to potential out-of-bounds writes or + potential denial of service. + +* The ProgramData configuration file is always read for compatibility + with Git for Windows and Portable Git installations. The ProgramData + location is not necessarily writable only by administrators, so we + now ensure that the configuration file is owned by the administrator + or the current user. + +### API additions + +* The SSH host key now supports SHA-256 when `GIT_CERT_SSH_SHA256` is set. + +* The diff format option `GIT_DIFF_FORMAT_PATCH_ID` can now be used to + emit an output like `git patch-id`. + +* The `git_apply_options_init` function will initialize a + `git_apply_options` structure. + +* The remote callbacks structure adds a `git_url_resolve_cb` callback + that is invoked when connecting to a server, so that applications + may edit or replace the URL before connection. + +* The information about the original `HEAD` in a rebase operation is + available with `git_rebase_orig_head_name`. Its ID is available with + `git_rebase_orig_head_id`. The `onto` reference name is available with + `git_rebase_onto_name` and its ID is available with `git_rebase_onto_id`. + +* ODB backends can now free backend data when an error occurs during its + backend data creation using `git_odb_backend_data_free`. + +* Options may be specified to `git_repository_foreach_head` to control + its behavior: `GIT_REPOSITORY_FOREACH_HEAD_SKIP_REPO` will not skip + the main repository's HEAD reference, while + `GIT_REPOSITORY_FOREACH_HEAD_SKIP_WORKTREES` will now skip the + worktree HEAD references. + +* The `GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS` option can be specified to + `git_libgit2_opts()` to avoid looking for `.keep` files that correspond + to packfiles. This setting can improve performance when packfiles are + stored on high-latency filesystems like network filesystems. + +* Blobs can now be filtered with `git_blob_filter`, which allows for + options to be set with `git_blob_filter_options`, including + `GIT_FILTER_NO_SYSTEM_ATTRIBUTES` to disable filtering with system-level + attributes in `/etc/gitattributes` and `GIT_ATTR_CHECK_INCLUDE_HEAD` to + enable filtering with `.gitattributes` files in the HEAD revision. + +### API removals + +* The unused `git_headlist_cb` function declaration was removed. + +* The unused `git_time_monotonic` API is removed. + +* The erroneously exported `inttypes.h` header was removed. + +# Security Fixes - CVE-2019-1348: the fast-import stream command "feature export-marks=path" allows writing to arbitrary file paths. As @@ -58,54 +423,86 @@ This is a security release fixing the following issues: recursive submodule clones manually are encouraged to review their implementation for this vulnerability. -v0.28.3 -------- +### Breaking API changes -This is a security release fixing the following issues: +* The "private" implementation details of the `git_cred` structure have been + moved to a dedicated `git2/sys/cred.h` header, to clarify that the underlying + structures are only provided for custom transport implementers. + The breaking change is that the `username` member of the underlying struct + is now hidden, and a new `git_cred_get_username` function has been provided. -* A carefully constructed commit object with a very large number - of parents may lead to potential out-of-bounds writes or - potential denial of service. +### Breaking CMake configuration changes -* The ProgramData configuration file is always read for compatibility - with Git for Windows and Portable Git installations. The ProgramData - location is not necessarily writable only by administrators, so we - now ensure that the configuration file is owned by the administrator - or the current user. +* The CMake option to use a system http-parser library, instead of the + bundled dependency, has changed. This is due to a deficiency in + http-parser that we have fixed in our implementation. The bundled + library is now the default, but if you wish to force the use of the + system http-parser implementation despite incompatibilities, you can + specify `-DUSE_HTTP_PARSER=system` to CMake. -v0.28.2 -------- +* The interactions between `USE_HTTPS` and `SHA1_BACKEND` have been + streamlined. The detection was moved to a new `USE_SHA1`, modeled after + `USE_HTTPS`, which takes the values "CollisionDetection/Backend/Generic", to + better match how the "hashing backend" is selected, the default (ON) being + "CollisionDetection". If you were using `SHA1_BACKEND` previously, you'll + need to check the value you've used, or switch to the autodetection. -This is a bugfix release with the following changes: +### Authors -* Fix include directory ordering when using bundled dependencies. +The following individuals provided changes that were included in this +release: -* Fix infinite loop when searching for a non-existing repository with - Windows-style paths including drive prefixes. - -* Fix paths with a trailing "/" not always being treated as - directories when computing ignores. - -* Fix false negatives when computing ignores where ignore rules - that are a prefix to a negative ignore rule exist. - -* Fix patches with CRLF line endings not being parsed correctly. - -* Fix segfault when parsing patches with file addition (deletion) - where the added (deleted) file name contains a space. - -* Fix assertion failure when trying to write to a non-existent - locked configuration file. - -v0.28.1 -------- - -This is a bugfix release with the following change: - -* The deprecated functions (`git_buf_free` and the `giterr_` family of - functions) are now exported properly. In the v0.28 release, they were - not given the correct external attributes and they did not have the - correct linkage visibility in the v0.28 library. +* Aaron Patterson +* Alberto Fanjul +* Anders Borum +* Augie Fackler +* Augustin Fabre +* Ayush Shridhar +* brian m. carlson +* buddyspike +* Carlos Martín Nieto +* cheese1 +* Dan Skorupski +* Daniel Cohen Gindi +* Dave Lee +* David Brooks +* David Turner +* Denis Laxalde +* Dhruva Krishnamurthy +* Dominik Ritter +* Drew DeVault +* Edward Thomson +* Eric Huss +* Erik Aigner +* Etienne Samson +* Gregory Herrero +* Heiko Voigt +* Ian Hattendorf +* Jacques Germishuys +* Janardhan Pulivarthi +* Jason Haslam +* Johannes Schindelin +* Jordan Wallet +* Josh Bleecher Snyder +* kas +* kdj0c +* Laurence McGlashan +* lhchavez +* Lukas Berk +* Max Kostyukevich +* Patrick Steinhardt +* pcpthm +* Remy Suen +* Robert Coup +* romkatv +* Scott Furry +* Sebastian Henke +* Stefan Widgren +* Steve King Jr +* Sven Strickroth +* Tobias Nießen +* Tyler Ang-Wanek +* Tyler Wanek v0.28 ----- diff --git a/docs/fuzzing.md b/docs/fuzzing.md index cd825766b..25b32cb04 100644 --- a/docs/fuzzing.md +++ b/docs/fuzzing.md @@ -3,7 +3,7 @@ libgit2 is currently using [libFuzzer](https://libfuzzer.info) to perform automated fuzz testing. libFuzzer only works with clang. -## Prerequisites** for building fuzz targets: +## Prerequisites for building fuzz targets: 1. All the prerequisites for [building libgit2](https://github.com/libgit2/libgit2). 2. A recent version of clang. 6.0 is preferred. [pre-build Debian/Ubuntu @@ -27,14 +27,15 @@ automated fuzz testing. libFuzzer only works with clang. ## Run the fuzz targets -1. `ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolize-6.0 +1. `ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolize LSAN_OPTIONS=allocator_may_return_null=1 - ASAN_OPTIONS=allocator_may_return_null=1 ./build/fuzz/fuzz_packfile_raw - fuzz/corpora/fuzz_packfile_raw/` + ASAN_OPTIONS=allocator_may_return_null=1 ./build/fuzzers/packfile_fuzzer + fuzzers/corpora/packfile/` The `LSAN_OPTIONS` and `ASAN_OPTIONS` are there to allow `malloc(3)` to return -`NULL`. The `LLVM_PROFILE_FILE` is there to override the path where libFuzzer -will write the coverage report. +`NULL`, which is expected if a huge chunk of memory is allocated. The +`LLVM_PROFILE_FILE` environment string can also be added to override the path +where libFuzzer will write the coverage report. ## Get coverage diff --git a/docs/release.md b/docs/release.md index a8eff056d..22b35ede7 100644 --- a/docs/release.md +++ b/docs/release.md @@ -68,7 +68,7 @@ Here we do not use release candidates as the changes are supposed to be small an This is the same as a maintenance release, except that the fix itself will most likely be developed in a private repository and will only be visible to a select group of people until the release. -Everything else remains the same. Occasionally we might opt to backport a security fix to the previous series, based on how recently we started the new series and how serious the issue is. +We have committed to providing security fixes for the latest two released versions. E.g. if the latest version is v0.28.x, then we will provide security fixes for both v0.28.x and v0.27.y. ## Updating documentation diff --git a/docs/threading.md b/docs/threading.md index 7dd37444f..ddfaf7ae5 100644 --- a/docs/threading.md +++ b/docs/threading.md @@ -1,8 +1,10 @@ -Threads in libgit2 +Threading in libgit2 ================== -You may safely use any libgit2 object from any thread, though there -may be issues depending on the cryptographic libraries libgit2 or its +Unless otherwise specified, libgit2 objects cannot be safely accessed by +multiple threads simultaneously. + +There are also caveats on the cryptographic libraries libgit2 or its dependencies link to (more on this later). For libgit2 itself, provided you take the following into consideration you won't run into issues: @@ -26,11 +28,11 @@ The error message is thread-local. The `git_error_last()` call must happen on the same thread as the error in order to get the message. Often this will be the case regardless, but if you use something like the [GCD](http://en.wikipedia.org/wiki/Grand_Central_Dispatch) -on Mac OS X (where code is executed on an arbitrary thread), the code +on macOS (where code is executed on an arbitrary thread), the code must make sure to retrieve the error code on the thread where the error happened. -Threads and cryptographic libraries +Threading and cryptographic libraries ======================================= On Windows @@ -44,17 +46,11 @@ steps necessary. If you are using a MinGW or similar environment where libssh2 uses OpenSSL or libgcrypt, then the general case affects you. -On Mac OS X +On macOS ----------- -By default we use libcurl to perform the encryption. The -system-provided libcurl uses SecureTransport, so no special steps are -necessary. If you link against another libcurl (e.g. from homebrew) -refer to the general case. - -If the option to use libcurl was deactivated, the library makes use of -CommonCrypto and SecureTransport for cryptographic support. These are -thread-safe and you do not need to do anything special. +By default we make use of CommonCrypto and SecureTransport for cryptographic +support. These are thread-safe and you do not need to do anything special. Note that libssh2 may still use OpenSSL itself. In that case, the general case still affects you if you use ssh. @@ -62,15 +58,11 @@ general case still affects you if you use ssh. General Case ------------ -If it's available, by default we use libcurl to provide HTTP tunneling support, -which may be linked against a number of cryptographic libraries and has its -own -[recommendations for thread safety](https://curl.haxx.se/libcurl/c/threadsafe.html). - -If there are no alternative TLS implementations (currently only -SecureTransport), libgit2 uses OpenSSL in order to use HTTPS as a transport. -OpenSSL is thread-safe starting at version 1.1.0. If your copy of libgit2 is -linked against that version, you do not need to take any further steps. +libgit2 will default to OpenSSL for HTTPS transport (except on Windows and +macOS, as mentioned above). On any system, mbedTLS _may_ be optionally +enabled as the security provider. OpenSSL is thread-safe starting at +version 1.1.0. If your copy of libgit2 is linked against that version, +you do not need to take any further steps. Older versions of OpenSSL are made to be thread-implementation agnostic, and the users of the library must set which locking function it should use. libgit2 @@ -78,10 +70,10 @@ cannot know what to set as the user of libgit2 may also be using OpenSSL indepen the locking settings must then live outside the lifetime of libgit2. Even if libgit2 doesn't use OpenSSL directly, OpenSSL can still be used by -libssh2 or libcurl depending on the configuration. If OpenSSL is used by +libssh2 depending on the configuration. If OpenSSL is used by more than one library, you only need to set up threading for OpenSSL once. -If libgit2 is linked against OpenSSL, it provides a last-resort convenience function +If libgit2 is linked against OpenSSL < 1.1.0, it provides a last-resort convenience function `git_openssl_set_locking()` (available in `sys/openssl.h`) to use the platform-native mutex mechanisms to perform the locking, which you can use if you do not want to use OpenSSL outside of libgit2, or you @@ -89,7 +81,7 @@ know that libgit2 will outlive the rest of the operations. It is then not safe to use OpenSSL multi-threaded after libgit2's shutdown function has been called. Note `git_openssl_set_locking()` only works if libgit2 uses OpenSSL directly - if OpenSSL is only used as a dependency -of libssh2 or libcurl as described above, `git_openssl_set_locking()` is a no-op. +of libssh2 as described above, `git_openssl_set_locking()` is a no-op. If your programming language offers a package/bindings for OpenSSL, you should very strongly prefer to use that in order to set up diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e631b96f2..8cc72b35e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,25 +1,15 @@ INCLUDE_DIRECTORIES(${LIBGIT2_INCLUDES}) INCLUDE_DIRECTORIES(SYSTEM ${LIBGIT2_SYSTEM_INCLUDES}) -FILE(GLOB_RECURSE SRC_EXAMPLE_GIT2 network/*.c network/*.h common.?) -ADD_EXECUTABLE(cgit2 ${SRC_EXAMPLE_GIT2}) -SET_TARGET_PROPERTIES(cgit2 PROPERTIES C_STANDARD 90) +FILE(GLOB LG2_SOURCES *.c *.h) +ADD_EXECUTABLE(lg2 ${LG2_SOURCES}) +SET_TARGET_PROPERTIES(lg2 PROPERTIES C_STANDARD 90) # Ensure that we do not use deprecated functions internally ADD_DEFINITIONS(-DGIT_DEPRECATE_HARD) IF(WIN32 OR ANDROID) - TARGET_LINK_LIBRARIES(cgit2 git2) + TARGET_LINK_LIBRARIES(lg2 git2) ELSE() - TARGET_LINK_LIBRARIES(cgit2 git2 pthread) + TARGET_LINK_LIBRARIES(lg2 git2 pthread) ENDIF() - -FILE(GLOB SRC_EXAMPLE_APPS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c) -FOREACH(src_app ${SRC_EXAMPLE_APPS}) - STRING(REPLACE ".c" "" app_name ${src_app}) - IF(NOT ${app_name} STREQUAL "common") - ADD_EXECUTABLE(${app_name} ${src_app} "common.c") - TARGET_LINK_LIBRARIES(${app_name} git2) - SET_TARGET_PROPERTIES(${app_name} PROPERTIES C_STANDARD 90) - ENDIF() -ENDFOREACH() diff --git a/examples/add.c b/examples/add.c index e5849892e..6e3c239fc 100644 --- a/examples/add.c +++ b/examples/add.c @@ -13,85 +13,97 @@ */ #include "common.h" -#include -enum print_options { - SKIP = 1, - VERBOSE = 2, - UPDATE = 4, +/** + * The following example demonstrates how to add files with libgit2. + * + * It will use the repository in the current working directory, and act + * on files passed as its parameters. + * + * Recognized options are: + * -v/--verbose: show the file's status after acting on it. + * -n/--dry-run: do not actually change the index. + * -u/--update: update the index instead of adding to it. + */ + +enum index_mode { + INDEX_NONE, + INDEX_ADD, }; -struct print_payload { - enum print_options options; +struct index_options { + int dry_run; + int verbose; git_repository *repo; + enum index_mode mode; + int add_update; }; /* Forward declarations for helpers */ -static void parse_opts(int *options, int *count, int argc, char *argv[]); -void init_array(git_strarray *array, int argc, char **argv); +static void parse_opts(const char **repo_path, struct index_options *opts, struct args_info *args); int print_matched_cb(const char *path, const char *matched_pathspec, void *payload); -int main (int argc, char** argv) +int lg2_add(git_repository *repo, int argc, char **argv) { git_index_matched_path_cb matched_cb = NULL; - git_repository *repo = NULL; git_index *index; git_strarray array = {0}; - int options = 0, count = 0; - struct print_payload payload = {0}; + struct index_options options; + struct args_info args = ARGS_INFO_INIT; - git_libgit2_init(); + /* Parse the options & arguments. */ + parse_opts(NULL, &options, &args); + strarray_from_args(&array, &args); - parse_opts(&options, &count, argc, argv); - - init_array(&array, argc-count, argv+count); - - check_lg2(git_repository_open(&repo, "."), "No git repository", NULL); + /* Grab the repository's index. */ check_lg2(git_repository_index(&index, repo), "Could not open repository index", NULL); - if (options&VERBOSE || options&SKIP) { + /* Setup a callback if the requested options need it */ + if (options.verbose || options.dry_run) { matched_cb = &print_matched_cb; } - payload.options = options; - payload.repo = repo; + options.repo = repo; - if (options&UPDATE) { - git_index_update_all(index, &array, matched_cb, &payload); + /* Perform the requested action with the index and files */ + if (options.add_update) { + git_index_update_all(index, &array, matched_cb, &options); } else { - git_index_add_all(index, &array, 0, matched_cb, &payload); + git_index_add_all(index, &array, 0, matched_cb, &options); } + /* Cleanup memory */ git_index_write(index); git_index_free(index); - git_repository_free(repo); - - git_libgit2_shutdown(); return 0; } +/* + * This callback is called for each file under consideration by + * git_index_(update|add)_all above. + * It makes uses of the callback's ability to abort the action. + */ int print_matched_cb(const char *path, const char *matched_pathspec, void *payload) { - struct print_payload p = *(struct print_payload*)(payload); + struct index_options *opts = (struct index_options *)(payload); int ret; unsigned status; (void)matched_pathspec; - if (git_status_file(&status, p.repo, path)) { + /* Get the file status */ + if (git_status_file(&status, opts->repo, path) < 0) return -1; - } - if (status & GIT_STATUS_WT_MODIFIED || status & GIT_STATUS_WT_NEW) { + if ((status & GIT_STATUS_WT_MODIFIED) || (status & GIT_STATUS_WT_NEW)) { printf("add '%s'\n", path); ret = 0; } else { ret = 1; } - if(p.options & SKIP) { + if (opts->dry_run) ret = 1; - } return ret; } @@ -101,11 +113,11 @@ void init_array(git_strarray *array, int argc, char **argv) unsigned int i; array->count = argc; - array->strings = malloc(sizeof(char*) * array->count); - assert(array->strings!=NULL); + array->strings = calloc(array->count, sizeof(char *)); + assert(array->strings != NULL); - for(i=0; icount; i++) { - array->strings[i]=argv[i]; + for (i = 0; i < array->count; i++) { + array->strings[i] = argv[i]; } return; @@ -120,39 +132,39 @@ void print_usage(void) exit(1); } -static void parse_opts(int *options, int *count, int argc, char *argv[]) +static void parse_opts(const char **repo_path, struct index_options *opts, struct args_info *args) { - int i; + if (args->argc <= 1) + print_usage(); - for (i = 1; i < argc; ++i) { - if (argv[i][0] != '-') { - break; - } - else if(!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) { - *options |= VERBOSE; - } - else if(!strcmp(argv[i], "--dry-run") || !strcmp(argv[i], "-n")) { - *options |= SKIP; - } - else if(!strcmp(argv[i], "--update") || !strcmp(argv[i], "-u")) { - *options |= UPDATE; - } - else if(!strcmp(argv[i], "-h")) { + for (args->pos = 1; args->pos < args->argc; ++args->pos) { + const char *curr = args->argv[args->pos]; + + if (curr[0] != '-') { + if (!strcmp("add", curr)) { + opts->mode = INDEX_ADD; + continue; + } else if (opts->mode == INDEX_NONE) { + fprintf(stderr, "missing command: %s", curr); + print_usage(); + break; + } else { + /* We might be looking at a filename */ + break; + } + } else if (match_bool_arg(&opts->verbose, args, "--verbose") || + match_bool_arg(&opts->dry_run, args, "--dry-run") || + match_str_arg(repo_path, args, "--git-dir") || + (opts->mode == INDEX_ADD && match_bool_arg(&opts->add_update, args, "--update"))) { + continue; + } else if (match_bool_arg(NULL, args, "--help")) { print_usage(); break; - } - else if(!strcmp(argv[i], "--")) { - i++; + } else if (match_arg_separator(args)) { break; - } - else { - fprintf(stderr, "Unsupported option %s.\n", argv[i]); + } else { + fprintf(stderr, "Unsupported option %s.\n", curr); print_usage(); } } - - if (argc<=i) - print_usage(); - - *count = i; } diff --git a/examples/args.c b/examples/args.c new file mode 100644 index 000000000..533e1579b --- /dev/null +++ b/examples/args.c @@ -0,0 +1,197 @@ +#include "common.h" +#include "args.h" + +size_t is_prefixed(const char *str, const char *pfx) +{ + size_t len = strlen(pfx); + return strncmp(str, pfx, len) ? 0 : len; +} + +int optional_str_arg( + const char **out, struct args_info *args, const char *opt, const char *def) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return 0; + + if (!found[len]) { + if (args->pos + 1 == args->argc) { + *out = def; + return 1; + } + args->pos += 1; + *out = args->argv[args->pos]; + return 1; + } + + if (found[len] == '=') { + *out = found + len + 1; + return 1; + } + + return 0; +} + +int match_str_arg( + const char **out, struct args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return 0; + + if (!found[len]) { + if (args->pos + 1 == args->argc) + fatal("expected value following argument", opt); + args->pos += 1; + *out = args->argv[args->pos]; + return 1; + } + + if (found[len] == '=') { + *out = found + len + 1; + return 1; + } + + return 0; +} + +static const char *match_numeric_arg(struct args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return NULL; + + if (!found[len]) { + if (args->pos + 1 == args->argc) + fatal("expected numeric value following argument", opt); + args->pos += 1; + found = args->argv[args->pos]; + } else { + found = found + len; + if (*found == '=') + found++; + } + + return found; +} + +int match_uint16_arg( + uint16_t *out, struct args_info *args, const char *opt) +{ + const char *found = match_numeric_arg(args, opt); + uint16_t val; + char *endptr = NULL; + + if (!found) + return 0; + + val = (uint16_t)strtoul(found, &endptr, 0); + if (!endptr || *endptr != '\0') + fatal("expected number after argument", opt); + + if (out) + *out = val; + return 1; +} + +int match_uint32_arg( + uint32_t *out, struct args_info *args, const char *opt) +{ + const char *found = match_numeric_arg(args, opt); + uint16_t val; + char *endptr = NULL; + + if (!found) + return 0; + + val = (uint32_t)strtoul(found, &endptr, 0); + if (!endptr || *endptr != '\0') + fatal("expected number after argument", opt); + + if (out) + *out = val; + return 1; +} + +static int match_int_internal( + int *out, const char *str, int allow_negative, const char *opt) +{ + char *endptr = NULL; + int val = (int)strtol(str, &endptr, 10); + + if (!endptr || *endptr != '\0') + fatal("expected number", opt); + else if (val < 0 && !allow_negative) + fatal("negative values are not allowed", opt); + + if (out) + *out = val; + + return 1; +} + +int match_bool_arg(int *out, struct args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + + if (!strcmp(found, opt)) { + *out = 1; + return 1; + } + + if (!strncmp(found, "--no-", strlen("--no-")) && + !strcmp(found + strlen("--no-"), opt + 2)) { + *out = 0; + return 1; + } + + *out = -1; + return 0; +} + +int is_integer(int *out, const char *str, int allow_negative) +{ + return match_int_internal(out, str, allow_negative, NULL); +} + +int match_int_arg( + int *out, struct args_info *args, const char *opt, int allow_negative) +{ + const char *found = match_numeric_arg(args, opt); + if (!found) + return 0; + return match_int_internal(out, found, allow_negative, opt); +} + +int match_arg_separator(struct args_info *args) +{ + if (args->opts_done) + return 1; + + if (strcmp(args->argv[args->pos], "--") != 0) + return 0; + + args->opts_done = 1; + args->pos++; + return 1; +} + +void strarray_from_args(git_strarray *array, struct args_info *args) +{ + size_t i; + + array->count = args->argc - args->pos; + array->strings = calloc(array->count, sizeof(char *)); + assert(array->strings != NULL); + + for (i = 0; args->pos < args->argc; ++args->pos) { + array->strings[i++] = args->argv[args->pos]; + } + args->pos = args->argc; +} diff --git a/examples/args.h b/examples/args.h new file mode 100644 index 000000000..d626f98c8 --- /dev/null +++ b/examples/args.h @@ -0,0 +1,90 @@ +#ifndef INCLUDE_examples_args_h__ +#define INCLUDE_examples_args_h__ + +/** + * Argument-processing helper structure + */ +struct args_info { + int argc; + char **argv; + int pos; + int opts_done : 1; /**< Did we see a -- separator */ +}; +#define ARGS_INFO_INIT { argc, argv, 0, 0 } +#define ARGS_CURRENT(args) args->argv[args->pos] + +/** + * Check if a string has the given prefix. Returns 0 if not prefixed + * or the length of the prefix if it is. + */ +extern size_t is_prefixed(const char *str, const char *pfx); + +/** + * Match an integer string, returning 1 if matched, 0 if not. + */ +extern int is_integer(int *out, const char *str, int allow_negative); + +/** + * Check current `args` entry against `opt` string. If it matches + * exactly, take the next arg as a string; if it matches as a prefix with + * an equal sign, take the remainder as a string; if value not supplied, + * default value `def` will be given. otherwise return 0. + */ +extern int optional_str_arg( + const char **out, struct args_info *args, const char *opt, const char *def); + +/** + * Check current `args` entry against `opt` string. If it matches + * exactly, take the next arg as a string; if it matches as a prefix with + * an equal sign, take the remainder as a string; otherwise return 0. + */ +extern int match_str_arg( + const char **out, struct args_info *args, const char *opt); + +/** + * Check current `args` entry against `opt` string parsing as uint16. If + * `opt` matches exactly, take the next arg as a uint16_t value; if `opt` + * is a prefix (equal sign optional), take the remainder of the arg as a + * uint16_t value; otherwise return 0. + */ +extern int match_uint16_arg( + uint16_t *out, struct args_info *args, const char *opt); + +/** + * Check current `args` entry against `opt` string parsing as uint32. If + * `opt` matches exactly, take the next arg as a uint16_t value; if `opt` + * is a prefix (equal sign optional), take the remainder of the arg as a + * uint32_t value; otherwise return 0. + */ +extern int match_uint32_arg( + uint32_t *out, struct args_info *args, const char *opt); + +/** + * Check current `args` entry against `opt` string parsing as int. If + * `opt` matches exactly, take the next arg as an int value; if it matches + * as a prefix (equal sign optional), take the remainder of the arg as a + * int value; otherwise return 0. + */ +extern int match_int_arg( + int *out, struct args_info *args, const char *opt, int allow_negative); + +/** + * Check current `args` entry against a "bool" `opt` (ie. --[no-]progress). + * If `opt` matches positively, out will be set to 1, or if `opt` matches + * negatively, out will be set to 0, and in both cases 1 will be returned. + * If neither the positive or the negative form of opt matched, out will be -1, + * and 0 will be returned. + */ +extern int match_bool_arg(int *out, struct args_info *args, const char *opt); + +/** + * Check if we're processing past the single -- separator + */ +extern int match_arg_separator(struct args_info *args); + +/** + * Consume all remaining arguments in a git_strarray + */ +extern void strarray_from_args(git_strarray *array, struct args_info *args); + +#endif diff --git a/examples/blame.c b/examples/blame.c index 9288352e2..49350fc0f 100644 --- a/examples/blame.c +++ b/examples/blame.c @@ -14,17 +14,12 @@ #include "common.h" -#ifdef _MSC_VER -#define snprintf sprintf_s -#define strcasecmp strcmpi -#endif - /** * This example demonstrates how to invoke the libgit2 blame API to roughly * simulate the output of `git blame` and a few of its command line arguments. */ -struct opts { +struct blame_opts { char *path; char *commitspec; int C; @@ -33,32 +28,26 @@ struct opts { int end_line; int F; }; -static void parse_opts(struct opts *o, int argc, char *argv[]); +static void parse_opts(struct blame_opts *o, int argc, char *argv[]); -int main(int argc, char *argv[]) +int lg2_blame(git_repository *repo, int argc, char *argv[]) { int line, break_on_null_hunk; - size_t i, rawsize; + git_object_size_t i, rawsize; char spec[1024] = {0}; - struct opts o = {0}; + struct blame_opts o = {0}; const char *rawdata; - git_repository *repo = NULL; git_revspec revspec = {0}; git_blame_options blameopts = GIT_BLAME_OPTIONS_INIT; git_blame *blame = NULL; git_blob *blob; git_object *obj; - git_libgit2_init(); - parse_opts(&o, argc, argv); if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; if (o.F) blameopts.flags |= GIT_BLAME_FIRST_PARENT; - /** Open the repository. */ - check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), "Couldn't open repository", NULL); - /** * The commit range comes in "commitish" form. Use the rev-parse API to * nail down the end points. @@ -83,7 +72,7 @@ int main(int argc, char *argv[]) * Get the raw data inside the blob for output. We use the * `commitish:path/to/file.txt` format to find it. */ - if (git_oid_iszero(&blameopts.newest_commit)) + if (git_oid_is_zero(&blameopts.newest_commit)) strcpy(spec, "HEAD"); else git_oid_tostr(spec, sizeof(spec), &blameopts.newest_commit); @@ -102,7 +91,7 @@ int main(int argc, char *argv[]) i = 0; break_on_null_hunk = 0; while (i < rawsize) { - const char *eol = memchr(rawdata + i, '\n', rawsize - i); + const char *eol = memchr(rawdata + i, '\n', (size_t)(rawsize - i)); char oid[10] = {0}; const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line); @@ -112,7 +101,7 @@ int main(int argc, char *argv[]) if (hunk) { char sig[128] = {0}; break_on_null_hunk = 1; - + git_oid_tostr(oid, 10, &hunk->final_commit_id); snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email); @@ -131,9 +120,6 @@ int main(int argc, char *argv[]) /** Cleanup. */ git_blob_free(blob); git_blame_free(blame); - git_repository_free(repo); - - git_libgit2_shutdown(); return 0; } @@ -157,7 +143,7 @@ static void usage(const char *msg, const char *arg) } /** Parse the arguments. */ -static void parse_opts(struct opts *o, int argc, char *argv[]) +static void parse_opts(struct blame_opts *o, int argc, char *argv[]) { int i; char *bare_args[3] = {0}; diff --git a/examples/cat-file.c b/examples/cat-file.c index 0fb7d4535..b81e9a8da 100644 --- a/examples/cat-file.c +++ b/examples/cat-file.c @@ -102,37 +102,33 @@ static void show_tag(const git_tag *tag) printf("\n%s\n", git_tag_message(tag)); } -enum { +typedef enum { SHOW_TYPE = 1, SHOW_SIZE = 2, SHOW_NONE = 3, SHOW_PRETTY = 4 -}; +} catfile_mode; /* Forward declarations for option-parsing helper */ -struct opts { +struct catfile_options { const char *dir; const char *rev; - int action; + catfile_mode action; int verbose; }; -static void parse_opts(struct opts *o, int argc, char *argv[]); + +static void parse_opts(struct catfile_options *o, int argc, char *argv[]); /** Entry point for this command */ -int main(int argc, char *argv[]) +int lg2_cat_file(git_repository *repo, int argc, char *argv[]) { - git_repository *repo; - struct opts o = { ".", NULL, 0, 0 }; + struct catfile_options o = { ".", NULL, 0, 0 }; git_object *obj = NULL; char oidstr[GIT_OID_HEXSZ + 1]; - git_libgit2_init(); - parse_opts(&o, argc, argv); - check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL), - "Could not open repository", NULL); check_lg2(git_revparse_single(&obj, repo, o.rev), "Could not resolve", o.rev); @@ -188,9 +184,6 @@ int main(int argc, char *argv[]) } git_object_free(obj); - git_repository_free(repo); - - git_libgit2_shutdown(); return 0; } @@ -209,7 +202,7 @@ static void usage(const char *message, const char *arg) } /** Parse the command-line options taken from git */ -static void parse_opts(struct opts *o, int argc, char *argv[]) +static void parse_opts(struct catfile_options *o, int argc, char *argv[]) { struct args_info args = ARGS_INFO_INIT; diff --git a/examples/checkout.c b/examples/checkout.c index 9119d6503..897c42f3f 100644 --- a/examples/checkout.c +++ b/examples/checkout.c @@ -13,7 +13,6 @@ */ #include "common.h" -#include /* Define the printf format specifer to use for size_t output */ #if defined(_MSC_VER) || defined(__MINGW32__) @@ -66,7 +65,7 @@ static void parse_options(const char **repo_path, checkout_options *opts, struct const char *curr = args->argv[args->pos]; int bool_arg; - if (strcmp(curr, "--") == 0) { + if (match_arg_separator(args)) { break; } else if (!strcmp(curr, "--force")) { opts->force = 1; @@ -112,9 +111,10 @@ static void print_perf_data(const git_checkout_perfdata *perfdata, void *payload * This is the main "checkout " function, responsible for performing * a branch-based checkout. */ -static int perform_checkout_ref(git_repository *repo, git_annotated_commit *target, checkout_options *opts) +static int perform_checkout_ref(git_repository *repo, git_annotated_commit *target, const char *target_ref, checkout_options *opts) { git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + git_reference *ref = NULL, *branch = NULL; git_commit *target_commit = NULL; int err; @@ -156,10 +156,25 @@ static int perform_checkout_ref(git_repository *repo, git_annotated_commit *targ * we might need to detach HEAD. */ if (git_annotated_commit_ref(target)) { - err = git_repository_set_head(repo, git_annotated_commit_ref(target)); + const char *target_head; + + if ((err = git_reference_lookup(&ref, repo, git_annotated_commit_ref(target))) < 0) + goto error; + + if (git_reference_is_remote(ref)) { + if ((err = git_branch_create_from_annotated(&branch, repo, target_ref, target, 0)) < 0) + goto error; + target_head = git_reference_name(branch); + } else { + target_head = git_annotated_commit_ref(target); + } + + err = git_repository_set_head(repo, target_head); } else { err = git_repository_set_head_detached_from_annotated(repo, target); } + +error: if (err != 0) { fprintf(stderr, "failed to update HEAD reference: %s\n", git_error_last()->message); goto cleanup; @@ -167,14 +182,70 @@ static int perform_checkout_ref(git_repository *repo, git_annotated_commit *targ cleanup: git_commit_free(target_commit); + git_reference_free(branch); + git_reference_free(ref); return err; } -/** That example's entry point */ -int main(int argc, char **argv) +/** + * This corresponds to `git switch --guess`: if a given ref does + * not exist, git will by default try to guess the reference by + * seeing whether any remote has a branch called . If there + * is a single remote only that has it, then it is assumed to be + * the desired reference and a local branch is created for it. + * + * The following is a simplified implementation. It will not try + * to check whether the ref is unique across all remotes. + */ +static int guess_refish(git_annotated_commit **out, git_repository *repo, const char *ref) +{ + git_strarray remotes = { NULL, 0 }; + git_reference *remote_ref = NULL; + int error; + size_t i; + + if ((error = git_remote_list(&remotes, repo)) < 0) + goto out; + + for (i = 0; i < remotes.count; i++) { + char *refname = NULL; + size_t reflen; + + reflen = snprintf(refname, 0, "refs/remotes/%s/%s", remotes.strings[i], ref); + if ((refname = malloc(reflen + 1)) == NULL) { + error = -1; + goto next; + } + snprintf(refname, reflen + 1, "refs/remotes/%s/%s", remotes.strings[i], ref); + + if ((error = git_reference_lookup(&remote_ref, repo, refname)) < 0) + goto next; + + break; +next: + free(refname); + if (error < 0 && error != GIT_ENOTFOUND) + break; + } + + if (!remote_ref) { + error = GIT_ENOTFOUND; + goto out; + } + + if ((error = git_annotated_commit_from_ref(out, repo, remote_ref)) < 0) + goto out; + +out: + git_reference_free(remote_ref); + git_strarray_free(&remotes); + return error; +} + +/** That example's entry point */ +int lg2_checkout(git_repository *repo, int argc, char **argv) { - git_repository *repo = NULL; struct args_info args = ARGS_INFO_INIT; checkout_options opts; git_repository_state_t state; @@ -185,15 +256,6 @@ int main(int argc, char **argv) /** Parse our command line options */ parse_options(&path, &opts, &args); - /** Initialize the library */ - err = git_libgit2_init(); - if (!err) - check_lg2(err, "Failed to initialize libgit2", NULL); - - /** Open the repository corresponding to the options */ - check_lg2(git_repository_open_ext(&repo, path, 0, NULL), - "Could not open repository", NULL); - /** Make sure we're not about to checkout while something else is going on */ state = git_repository_state(repo); if (state != GIT_REPOSITORY_STATE_NONE) { @@ -201,11 +263,7 @@ int main(int argc, char **argv) goto cleanup; } - if (args.pos >= args.argc) { - fprintf(stderr, "unhandled\n"); - err = -1; - goto cleanup; - } else if (strcmp("--", args.argv[args.pos])) { + if (match_arg_separator(&args)) { /** * Try to checkout the given path */ @@ -217,19 +275,16 @@ int main(int argc, char **argv) /** * Try to resolve a "refish" argument to a target libgit2 can use */ - err = resolve_refish(&checkout_target, repo, args.argv[args.pos]); - if (err != 0) { + if ((err = resolve_refish(&checkout_target, repo, args.argv[args.pos])) < 0 && + (err = guess_refish(&checkout_target, repo, args.argv[args.pos])) < 0) { fprintf(stderr, "failed to resolve %s: %s\n", args.argv[args.pos], git_error_last()->message); goto cleanup; } - err = perform_checkout_ref(repo, checkout_target, &opts); + err = perform_checkout_ref(repo, checkout_target, args.argv[args.pos], &opts); } cleanup: git_annotated_commit_free(checkout_target); - git_repository_free(repo); - git_libgit2_shutdown(); - return err; } diff --git a/examples/network/clone.c b/examples/clone.c similarity index 82% rename from examples/network/clone.c rename to examples/clone.c index bbcd2e848..22d9d9b61 100644 --- a/examples/network/clone.c +++ b/examples/clone.c @@ -1,16 +1,7 @@ #include "common.h" -#include -#include -#include -#include -#include -#ifndef _WIN32 -# include -# include -#endif typedef struct progress_data { - git_transfer_progress fetch_progress; + git_indexer_progress fetch_progress; size_t completed_steps; size_t total_steps; const char *path; @@ -26,17 +17,17 @@ static void print_progress(const progress_data *pd) 0; int checkout_percent = pd->total_steps > 0 - ? (100 * pd->completed_steps) / pd->total_steps + ? (int)((100 * pd->completed_steps) / pd->total_steps) : 0; - int kbytes = pd->fetch_progress.received_bytes / 1024; + size_t kbytes = pd->fetch_progress.received_bytes / 1024; if (pd->fetch_progress.total_objects && pd->fetch_progress.received_objects == pd->fetch_progress.total_objects) { - printf("Resolving deltas %d/%d\r", + printf("Resolving deltas %u/%u\r", pd->fetch_progress.indexed_deltas, pd->fetch_progress.total_deltas); } else { - printf("net %3d%% (%4d kb, %5d/%5d) / idx %3d%% (%5d/%5d) / chk %3d%% (%4" PRIuZ "/%4" PRIuZ ") %s\n", + printf("net %3d%% (%4" PRIuZ " kb, %5u/%5u) / idx %3d%% (%5u/%5u) / chk %3d%% (%4" PRIuZ "/%4" PRIuZ")%s\n", network_percent, kbytes, pd->fetch_progress.received_objects, pd->fetch_progress.total_objects, index_percent, pd->fetch_progress.indexed_objects, pd->fetch_progress.total_objects, @@ -55,7 +46,7 @@ static int sideband_progress(const char *str, int len, void *payload) return 0; } -static int fetch_progress(const git_transfer_progress *stats, void *payload) +static int fetch_progress(const git_indexer_progress *stats, void *payload) { progress_data *pd = (progress_data*)payload; pd->fetch_progress = *stats; @@ -72,7 +63,7 @@ static void checkout_progress(const char *path, size_t cur, size_t tot, void *pa } -int do_clone(git_repository *repo, int argc, char **argv) +int lg2_clone(git_repository *repo, int argc, char **argv) { progress_data pd = {{0}}; git_repository *cloned_repo = NULL; diff --git a/examples/common.c b/examples/common.c index e20767a9e..b068b8488 100644 --- a/examples/common.c +++ b/examples/common.c @@ -12,10 +12,14 @@ * . */ -#include #include "common.h" +#ifndef _WIN32 +# include +#endif +#include + void check_lg2(int error, const char *message, const char *extra) { const git_error *lg2err; @@ -49,174 +53,6 @@ void fatal(const char *message, const char *extra) exit(1); } -size_t is_prefixed(const char *str, const char *pfx) -{ - size_t len = strlen(pfx); - return strncmp(str, pfx, len) ? 0 : len; -} - -int optional_str_arg( - const char **out, struct args_info *args, const char *opt, const char *def) -{ - const char *found = args->argv[args->pos]; - size_t len = is_prefixed(found, opt); - - if (!len) - return 0; - - if (!found[len]) { - if (args->pos + 1 == args->argc) { - *out = def; - return 1; - } - args->pos += 1; - *out = args->argv[args->pos]; - return 1; - } - - if (found[len] == '=') { - *out = found + len + 1; - return 1; - } - - return 0; -} - -int match_str_arg( - const char **out, struct args_info *args, const char *opt) -{ - const char *found = args->argv[args->pos]; - size_t len = is_prefixed(found, opt); - - if (!len) - return 0; - - if (!found[len]) { - if (args->pos + 1 == args->argc) - fatal("expected value following argument", opt); - args->pos += 1; - *out = args->argv[args->pos]; - return 1; - } - - if (found[len] == '=') { - *out = found + len + 1; - return 1; - } - - return 0; -} - -static const char *match_numeric_arg(struct args_info *args, const char *opt) -{ - const char *found = args->argv[args->pos]; - size_t len = is_prefixed(found, opt); - - if (!len) - return NULL; - - if (!found[len]) { - if (args->pos + 1 == args->argc) - fatal("expected numeric value following argument", opt); - args->pos += 1; - found = args->argv[args->pos]; - } else { - found = found + len; - if (*found == '=') - found++; - } - - return found; -} - -int match_uint16_arg( - uint16_t *out, struct args_info *args, const char *opt) -{ - const char *found = match_numeric_arg(args, opt); - uint16_t val; - char *endptr = NULL; - - if (!found) - return 0; - - val = (uint16_t)strtoul(found, &endptr, 0); - if (!endptr || *endptr != '\0') - fatal("expected number after argument", opt); - - if (out) - *out = val; - return 1; -} - -int match_uint32_arg( - uint32_t *out, struct args_info *args, const char *opt) -{ - const char *found = match_numeric_arg(args, opt); - uint16_t val; - char *endptr = NULL; - - if (!found) - return 0; - - val = (uint32_t)strtoul(found, &endptr, 0); - if (!endptr || *endptr != '\0') - fatal("expected number after argument", opt); - - if (out) - *out = val; - return 1; -} - -static int match_int_internal( - int *out, const char *str, int allow_negative, const char *opt) -{ - char *endptr = NULL; - int val = (int)strtol(str, &endptr, 10); - - if (!endptr || *endptr != '\0') - fatal("expected number", opt); - else if (val < 0 && !allow_negative) - fatal("negative values are not allowed", opt); - - if (out) - *out = val; - - return 1; -} - -int match_bool_arg(int *out, struct args_info *args, const char *opt) -{ - const char *found = args->argv[args->pos]; - - if (!strcmp(found, opt)) { - *out = 1; - return 1; - } - - if (!strncmp(found, "--no-", strlen("--no-")) && - !strcmp(found + strlen("--no-"), opt + 2)) { - *out = 0; - return 1; - } - - *out = -1; - return 0; -} - -int is_integer(int *out, const char *str, int allow_negative) -{ - return match_int_internal(out, str, allow_negative, NULL); -} - -int match_int_arg( - int *out, struct args_info *args, const char *opt, int allow_negative) -{ - const char *found = match_numeric_arg(args, opt); - if (!found) - return 0; - return match_int_internal(out, found, allow_negative, opt); -} - int diff_output( const git_diff_delta *d, const git_diff_hunk *h, @@ -289,3 +125,136 @@ int resolve_refish(git_annotated_commit **commit, git_repository *repo, const ch return err; } + +static int readline(char **out) +{ + int c, error = 0, length = 0, allocated = 0; + char *line = NULL; + + errno = 0; + + while ((c = getchar()) != EOF) { + if (length == allocated) { + allocated += 16; + + if ((line = realloc(line, allocated)) == NULL) { + error = -1; + goto error; + } + } + + if (c == '\n') + break; + + line[length++] = c; + } + + if (errno != 0) { + error = -1; + goto error; + } + + line[length] = '\0'; + *out = line; + line = NULL; + error = length; +error: + free(line); + return error; +} + +static int ask(char **out, const char *prompt, char optional) +{ + printf("%s ", prompt); + fflush(stdout); + + if (!readline(out) && !optional) { + fprintf(stderr, "Could not read response: %s", strerror(errno)); + return -1; + } + + return 0; +} + +int cred_acquire_cb(git_credential **out, + const char *url, + const char *username_from_url, + unsigned int allowed_types, + void *payload) +{ + char *username = NULL, *password = NULL, *privkey = NULL, *pubkey = NULL; + int error = 1; + + UNUSED(url); + UNUSED(payload); + + if (username_from_url) { + if ((username = strdup(username_from_url)) == NULL) + goto out; + } else if ((error = ask(&username, "Username:", 0)) < 0) { + goto out; + } + + if (allowed_types & GIT_CREDENTIAL_SSH_KEY) { + int n; + + if ((error = ask(&privkey, "SSH Key:", 0)) < 0 || + (error = ask(&password, "Password:", 1)) < 0) + goto out; + + if ((n = snprintf(NULL, 0, "%s.pub", privkey)) < 0 || + (pubkey = malloc(n + 1)) == NULL || + (n = snprintf(pubkey, n + 1, "%s.pub", privkey)) < 0) + goto out; + + error = git_credential_ssh_key_new(out, username, pubkey, privkey, password); + } else if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) { + if ((error = ask(&password, "Password:", 1)) < 0) + goto out; + + error = git_credential_userpass_plaintext_new(out, username, password); + } else if (allowed_types & GIT_CREDENTIAL_USERNAME) { + error = git_credential_username_new(out, username); + } + +out: + free(username); + free(password); + free(privkey); + free(pubkey); + return error; +} + +char *read_file(const char *path) +{ + ssize_t total = 0; + char *buf = NULL; + struct stat st; + int fd = -1; + + if ((fd = open(path, O_RDONLY)) < 0 || fstat(fd, &st) < 0) + goto out; + + if ((buf = malloc(st.st_size + 1)) == NULL) + goto out; + + while (total < st.st_size) { + ssize_t bytes = read(fd, buf + total, st.st_size - total); + if (bytes <= 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + free(buf); + buf = NULL; + goto out; + } + total += bytes; + } + + buf[total] = '\0'; + +out: + if (fd >= 0) + close(fd); + return buf; +} + diff --git a/examples/common.h b/examples/common.h index 1c7b2035e..c01561b48 100644 --- a/examples/common.h +++ b/examples/common.h @@ -11,11 +11,73 @@ * with this software. If not, see * . */ +#ifndef INCLUDE_examples_common_h__ +#define INCLUDE_examples_common_h__ +#include +#include +#include #include #include #include #include +#include + +#ifdef _WIN32 +# include +# include +# define open _open +# define read _read +# define close _close +# define ssize_t int +# define sleep(a) Sleep(a * 1000) +#else +# include +#endif + +#ifndef PRIuZ +/* Define the printf format specifer to use for size_t output */ +#if defined(_MSC_VER) || defined(__MINGW32__) +# define PRIuZ "Iu" +#else +# define PRIuZ "zu" +#endif +#endif + +#ifdef _MSC_VER +#define snprintf sprintf_s +#define strcasecmp strcmpi +#endif + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(*x)) +#define UNUSED(x) (void)(x) + +#include "args.h" + +extern int lg2_add(git_repository *repo, int argc, char **argv); +extern int lg2_blame(git_repository *repo, int argc, char **argv); +extern int lg2_cat_file(git_repository *repo, int argc, char **argv); +extern int lg2_checkout(git_repository *repo, int argc, char **argv); +extern int lg2_clone(git_repository *repo, int argc, char **argv); +extern int lg2_config(git_repository *repo, int argc, char **argv); +extern int lg2_describe(git_repository *repo, int argc, char **argv); +extern int lg2_diff(git_repository *repo, int argc, char **argv); +extern int lg2_fetch(git_repository *repo, int argc, char **argv); +extern int lg2_for_each_ref(git_repository *repo, int argc, char **argv); +extern int lg2_general(git_repository *repo, int argc, char **argv); +extern int lg2_index_pack(git_repository *repo, int argc, char **argv); +extern int lg2_init(git_repository *repo, int argc, char **argv); +extern int lg2_log(git_repository *repo, int argc, char **argv); +extern int lg2_ls_files(git_repository *repo, int argc, char **argv); +extern int lg2_ls_remote(git_repository *repo, int argc, char **argv); +extern int lg2_merge(git_repository *repo, int argc, char **argv); +extern int lg2_remote(git_repository *repo, int argc, char **argv); +extern int lg2_rev_list(git_repository *repo, int argc, char **argv); +extern int lg2_rev_parse(git_repository *repo, int argc, char **argv); +extern int lg2_show_index(git_repository *repo, int argc, char **argv); +extern int lg2_stash(git_repository *repo, int argc, char **argv); +extern int lg2_status(git_repository *repo, int argc, char **argv); +extern int lg2_tag(git_repository *repo, int argc, char **argv); /** * Check libgit2 error code, printing error to stderr on failure and @@ -23,82 +85,19 @@ */ extern void check_lg2(int error, const char *message, const char *extra); +/** + * Read a file into a buffer + * + * @param path The path to the file that shall be read + * @return NUL-terminated buffer if the file was successfully read, NULL-pointer otherwise + */ +extern char *read_file(const char *path); + /** * Exit the program, printing error to stderr */ extern void fatal(const char *message, const char *extra); -/** - * Check if a string has the given prefix. Returns 0 if not prefixed - * or the length of the prefix if it is. - */ -extern size_t is_prefixed(const char *str, const char *pfx); - -/** - * Match an integer string, returning 1 if matched, 0 if not. - */ -extern int is_integer(int *out, const char *str, int allow_negative); - -struct args_info { - int argc; - char **argv; - int pos; -}; -#define ARGS_INFO_INIT { argc, argv, 0 } - -/** - * Check current `args` entry against `opt` string. If it matches - * exactly, take the next arg as a string; if it matches as a prefix with - * an equal sign, take the remainder as a string; if value not supplied, - * default value `def` will be given. otherwise return 0. - */ -extern int optional_str_arg( - const char **out, struct args_info *args, const char *opt, const char *def); - -/** - * Check current `args` entry against `opt` string. If it matches - * exactly, take the next arg as a string; if it matches as a prefix with - * an equal sign, take the remainder as a string; otherwise return 0. - */ -extern int match_str_arg( - const char **out, struct args_info *args, const char *opt); - -/** - * Check current `args` entry against `opt` string parsing as uint16. If - * `opt` matches exactly, take the next arg as a uint16_t value; if `opt` - * is a prefix (equal sign optional), take the remainder of the arg as a - * uint16_t value; otherwise return 0. - */ -extern int match_uint16_arg( - uint16_t *out, struct args_info *args, const char *opt); - -/** - * Check current `args` entry against `opt` string parsing as uint32. If - * `opt` matches exactly, take the next arg as a uint16_t value; if `opt` - * is a prefix (equal sign optional), take the remainder of the arg as a - * uint32_t value; otherwise return 0. - */ -extern int match_uint32_arg( - uint32_t *out, struct args_info *args, const char *opt); - -/** - * Check current `args` entry against `opt` string parsing as int. If - * `opt` matches exactly, take the next arg as an int value; if it matches - * as a prefix (equal sign optional), take the remainder of the arg as a - * int value; otherwise return 0. - */ -extern int match_int_arg( - int *out, struct args_info *args, const char *opt, int allow_negative); - -/** - * Check current `args` entry against a "bool" `opt` (ie. --[no-]progress). - * If `opt` matches positively, out will be set to 1, or if `opt` matches - * negatively, out will be set to 0, and in both cases 1 will be returned. - * If neither the positive or the negative form of opt matched, out will be -1, - * and 0 will be returned. - */ -extern int match_bool_arg(int *out, struct args_info *args, const char *opt); - /** * Basic output function for plain text diff output * Pass `FILE*` such as `stdout` or `stderr` as payload (or NULL == `stdout`) @@ -122,3 +121,14 @@ extern void *xrealloc(void *oldp, size_t newsz); * Convert a refish to an annotated commit. */ extern int resolve_refish(git_annotated_commit **commit, git_repository *repo, const char *refish); + +/** + * Acquire credentials via command line + */ +extern int cred_acquire_cb(git_credential **out, + const char *url, + const char *username_from_url, + unsigned int allowed_types, + void *payload); + +#endif diff --git a/examples/config.c b/examples/config.c new file mode 100644 index 000000000..f7fa70e4d --- /dev/null +++ b/examples/config.c @@ -0,0 +1,62 @@ +/* + * libgit2 "config" example - shows how to use the config API + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "common.h" + +static int config_get(git_config *cfg, const char *key) +{ + git_config_entry *entry; + int error; + + if ((error = git_config_get_entry(&entry, cfg, key)) < 0) { + if (error != GIT_ENOTFOUND) + printf("Unable to get configuration: %s\n", git_error_last()->message); + return 1; + } + + puts(entry->value); + return 0; +} + +static int config_set(git_config *cfg, const char *key, const char *value) +{ + if (git_config_set_string(cfg, key, value) < 0) { + printf("Unable to set configuration: %s\n", git_error_last()->message); + return 1; + } + return 0; +} + +int lg2_config(git_repository *repo, int argc, char **argv) +{ + git_config *cfg; + int error; + + if ((error = git_repository_config(&cfg, repo)) < 0) { + printf("Unable to obtain repository config: %s\n", git_error_last()->message); + goto out; + } + + if (argc == 2) { + error = config_get(cfg, argv[1]); + } else if (argc == 3) { + error = config_set(cfg, argv[1], argv[2]); + } else { + printf("USAGE: %s config []\n", argv[0]); + error = 1; + } + +out: + return error; +} diff --git a/examples/describe.c b/examples/describe.c index 2005de4ae..1236272a1 100644 --- a/examples/describe.c +++ b/examples/describe.c @@ -13,7 +13,6 @@ */ #include "common.h" -#include /** * The following example partially reimplements the `git describe` command @@ -38,32 +37,30 @@ */ /** describe_options represents the parsed command line options */ -typedef struct { +struct describe_options { const char **commits; size_t commit_count; git_describe_options describe_options; git_describe_format_options format_options; -} describe_options; +}; -typedef struct args_info args_info; - -static void opts_add_commit(describe_options *opts, const char *commit) +static void opts_add_commit(struct describe_options *opts, const char *commit) { size_t sz; assert(opts != NULL); sz = ++opts->commit_count * sizeof(opts->commits[0]); - opts->commits = xrealloc(opts->commits, sz); + opts->commits = xrealloc((void *) opts->commits, sz); opts->commits[opts->commit_count - 1] = commit; } -static void do_describe_single(git_repository *repo, describe_options *opts, const char *rev) +static void do_describe_single(git_repository *repo, struct describe_options *opts, const char *rev) { git_object *commit; git_describe_result *describe_result; git_buf buf = { 0 }; - + if (rev) { check_lg2(git_revparse_single(&commit, repo, rev), "Failed to lookup rev", rev); @@ -81,7 +78,7 @@ static void do_describe_single(git_repository *repo, describe_options *opts, con printf("%s\n", buf.ptr); } -static void do_describe(git_repository *repo, describe_options *opts) +static void do_describe(git_repository *repo, struct describe_options *opts) { if (opts->commit_count == 0) do_describe_single(repo, opts, NULL); @@ -100,9 +97,9 @@ static void print_usage(void) } /** Parse command line arguments */ -static void parse_options(describe_options *opts, int argc, char **argv) +static void parse_options(struct describe_options *opts, int argc, char **argv) { - args_info args = ARGS_INFO_INIT; + struct args_info args = ARGS_INFO_INIT; for (args.pos = 1; args.pos < argc; ++args.pos) { const char *curr = argv[args.pos]; @@ -142,33 +139,24 @@ static void parse_options(describe_options *opts, int argc, char **argv) } /** Initialize describe_options struct */ -static void describe_options_init(describe_options *opts) +static void describe_options_init(struct describe_options *opts) { memset(opts, 0, sizeof(*opts)); opts->commits = NULL; opts->commit_count = 0; - git_describe_init_options(&opts->describe_options, GIT_DESCRIBE_OPTIONS_VERSION); - git_describe_init_format_options(&opts->format_options, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION); + git_describe_options_init(&opts->describe_options, GIT_DESCRIBE_OPTIONS_VERSION); + git_describe_format_options_init(&opts->format_options, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION); } -int main(int argc, char **argv) +int lg2_describe(git_repository *repo, int argc, char **argv) { - git_repository *repo; - describe_options opts; - - git_libgit2_init(); - - check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), - "Could not open repository", NULL); + struct describe_options opts; describe_options_init(&opts); parse_options(&opts, argc, argv); do_describe(repo, &opts); - git_repository_free(repo); - git_libgit2_shutdown(); - return 0; } diff --git a/examples/diff.c b/examples/diff.c index 1de0483a3..2305c8652 100644 --- a/examples/diff.c +++ b/examples/diff.c @@ -47,11 +47,12 @@ enum { CACHE_NONE = 2 }; -/** The 'opts' struct captures all the various parsed command line options. */ -struct opts { +/** The 'diff_options' struct captures all the various parsed command line options. */ +struct diff_options { git_diff_options diffopts; git_diff_find_options findopts; int color; + int no_index; int cache; int output; git_diff_format_t format; @@ -62,28 +63,23 @@ struct opts { /** These functions are implemented at the end */ static void usage(const char *message, const char *arg); -static void parse_opts(struct opts *o, int argc, char *argv[]); +static void parse_opts(struct diff_options *o, int argc, char *argv[]); static int color_printer( const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*); -static void diff_print_stats(git_diff *diff, struct opts *o); +static void diff_print_stats(git_diff *diff, struct diff_options *o); +static void compute_diff_no_index(git_diff **diff, struct diff_options *o); -int main(int argc, char *argv[]) +int lg2_diff(git_repository *repo, int argc, char *argv[]) { - git_repository *repo = NULL; git_tree *t1 = NULL, *t2 = NULL; git_diff *diff; - struct opts o = { + struct diff_options o = { GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT, - -1, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "." + -1, -1, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "." }; - git_libgit2_init(); - parse_opts(&o, argc, argv); - check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL), - "Could not open repository", o.dir); - /** * Possible argument patterns: * @@ -92,49 +88,54 @@ int main(int argc, char *argv[]) * * <sha1> * * --cached * * --nocache (don't use index data in diff at all) + * * --no-index <file1> <file2> * * nothing * * Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2> * are not supported in this example */ - if (o.treeish1) - treeish_to_tree(&t1, repo, o.treeish1); - if (o.treeish2) - treeish_to_tree(&t2, repo, o.treeish2); + if (o.no_index >= 0) { + compute_diff_no_index(&diff, &o); + } else { + if (o.treeish1) + treeish_to_tree(&t1, repo, o.treeish1); + if (o.treeish2) + treeish_to_tree(&t2, repo, o.treeish2); - if (t1 && t2) - check_lg2( - git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts), - "diff trees", NULL); - else if (o.cache != CACHE_NORMAL) { - if (!t1) - treeish_to_tree(&t1, repo, "HEAD"); - - if (o.cache == CACHE_NONE) + if (t1 && t2) check_lg2( - git_diff_tree_to_workdir(&diff, repo, t1, &o.diffopts), + git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts), + "diff trees", NULL); + else if (o.cache != CACHE_NORMAL) { + if (!t1) + treeish_to_tree(&t1, repo, "HEAD"); + + if (o.cache == CACHE_NONE) + check_lg2( + git_diff_tree_to_workdir(&diff, repo, t1, &o.diffopts), + "diff tree to working directory", NULL); + else + check_lg2( + git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts), + "diff tree to index", NULL); + } + else if (t1) + check_lg2( + git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts), "diff tree to working directory", NULL); else check_lg2( - git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts), - "diff tree to index", NULL); + git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts), + "diff index to working directory", NULL); + + /** Apply rename and copy detection if requested. */ + + if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0) + check_lg2( + git_diff_find_similar(diff, &o.findopts), + "finding renames and copies", NULL); } - else if (t1) - check_lg2( - git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts), - "diff tree to working directory", NULL); - else - check_lg2( - git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts), - "diff index to working directory", NULL); - - /** Apply rename and copy detection if requested. */ - - if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0) - check_lg2( - git_diff_find_similar(diff, &o.findopts), - "finding renames and copies", NULL); /** Generate simple output using libgit2 display helper. */ @@ -157,17 +158,45 @@ int main(int argc, char *argv[]) } /** Cleanup before exiting. */ - git_diff_free(diff); git_tree_free(t1); git_tree_free(t2); - git_repository_free(repo); - - git_libgit2_shutdown(); return 0; } +static void compute_diff_no_index(git_diff **diff, struct diff_options *o) { + git_patch *patch = NULL; + char *file1_str = NULL; + char *file2_str = NULL; + git_buf buf = {0}; + + if (!o->treeish1 || !o->treeish2) { + usage("two files should be provided as arguments", NULL); + } + file1_str = read_file(o->treeish1); + if (file1_str == NULL) { + usage("file cannot be read", o->treeish1); + } + file2_str = read_file(o->treeish2); + if (file2_str == NULL) { + usage("file cannot be read", o->treeish2); + } + check_lg2( + git_patch_from_buffers(&patch, file1_str, strlen(file1_str), o->treeish1, file2_str, strlen(file2_str), o->treeish2, &o->diffopts), + "patch buffers", NULL); + check_lg2( + git_patch_to_buf(&buf, patch), + "patch to buf", NULL); + check_lg2( + git_diff_from_buffer(diff, buf.ptr, buf.size), + "diff from patch", NULL); + git_patch_free(patch); + git_buf_dispose(&buf); + free(file1_str); + free(file2_str); +} + static void usage(const char *message, const char *arg) { if (message && arg) @@ -212,11 +241,10 @@ static int color_printer( } /** Parse arguments as copied from git-diff. */ -static void parse_opts(struct opts *o, int argc, char *argv[]) +static void parse_opts(struct diff_options *o, int argc, char *argv[]) { struct args_info args = ARGS_INFO_INIT; - for (args.pos = 1; args.pos < argc; ++args.pos) { const char *a = argv[args.pos]; @@ -233,9 +261,10 @@ static void parse_opts(struct opts *o, int argc, char *argv[]) o->output |= OUTPUT_DIFF; o->format = GIT_DIFF_FORMAT_PATCH; } - else if (!strcmp(a, "--cached")) + else if (!strcmp(a, "--cached")) { o->cache = CACHE_ONLY; - else if (!strcmp(a, "--nocache")) + if (o->no_index >= 0) usage("--cached and --no-index are incompatible", NULL); + } else if (!strcmp(a, "--nocache")) o->cache = CACHE_NONE; else if (!strcmp(a, "--name-only") || !strcmp(a, "--format=name")) o->format = GIT_DIFF_FORMAT_NAME_ONLY; @@ -248,7 +277,10 @@ static void parse_opts(struct opts *o, int argc, char *argv[]) o->format = GIT_DIFF_FORMAT_RAW; o->diffopts.id_abbrev = 40; } - else if (!strcmp(a, "--color")) + else if (!strcmp(a, "--no-index")) { + o->no_index = 0; + if (o->cache == CACHE_ONLY) usage("--cached and --no-index are incompatible", NULL); + } else if (!strcmp(a, "--color")) o->color = 0; else if (!strcmp(a, "--no-color")) o->color = -1; @@ -309,7 +341,7 @@ static void parse_opts(struct opts *o, int argc, char *argv[]) } /** Display diff output with "--stat", "--numstat", or "--shortstat" */ -static void diff_print_stats(git_diff *diff, struct opts *o) +static void diff_print_stats(git_diff *diff, struct diff_options *o) { git_diff_stats *stats; git_buf b = GIT_BUF_INIT_CONST(NULL, 0); diff --git a/examples/network/fetch.c b/examples/fetch.c similarity index 83% rename from examples/network/fetch.c rename to examples/fetch.c index 76b301193..2b5ed1112 100644 --- a/examples/network/fetch.c +++ b/examples/fetch.c @@ -1,12 +1,4 @@ #include "common.h" -#include -#include -#include -#include -#ifndef _WIN32 -# include -# include -#endif static int progress_cb(const char *str, int len, void *data) { @@ -29,7 +21,7 @@ static int update_cb(const char *refname, const git_oid *a, const git_oid *b, vo git_oid_fmt(b_str, b); b_str[GIT_OID_HEXSZ] = '\0'; - if (git_oid_iszero(a)) { + if (git_oid_is_zero(a)) { printf("[new] %.20s %s\n", b_str, refname); } else { git_oid_fmt(a_str, a); @@ -46,15 +38,15 @@ static int update_cb(const char *refname, const git_oid *a, const git_oid *b, vo * data. Most frontends will probably want to show a percentage and * the download rate. */ -static int transfer_progress_cb(const git_transfer_progress *stats, void *payload) +static int transfer_progress_cb(const git_indexer_progress *stats, void *payload) { (void)payload; if (stats->received_objects == stats->total_objects) { - printf("Resolving deltas %d/%d\r", + printf("Resolving deltas %u/%u\r", stats->indexed_deltas, stats->total_deltas); } else if (stats->total_objects > 0) { - printf("Received %d/%d objects (%d) in %" PRIuZ " bytes\r", + printf("Received %u/%u objects (%u) in %" PRIuZ " bytes\r", stats->received_objects, stats->total_objects, stats->indexed_objects, stats->received_bytes); } @@ -62,10 +54,10 @@ static int transfer_progress_cb(const git_transfer_progress *stats, void *payloa } /** Entry point for this command */ -int fetch(git_repository *repo, int argc, char **argv) +int lg2_fetch(git_repository *repo, int argc, char **argv) { git_remote *remote = NULL; - const git_transfer_progress *stats; + const git_indexer_progress *stats; git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; if (argc < 2) { @@ -100,10 +92,10 @@ int fetch(git_repository *repo, int argc, char **argv) */ stats = git_remote_stats(remote); if (stats->local_objects > 0) { - printf("\rReceived %d/%d objects in %" PRIuZ " bytes (used %d local objects)\n", + printf("\rReceived %u/%u objects in %" PRIuZ " bytes (used %u local objects)\n", stats->indexed_objects, stats->total_objects, stats->received_bytes, stats->local_objects); } else{ - printf("\rReceived %d/%d objects in %" PRIuZ "bytes\n", + printf("\rReceived %u/%u objects in %" PRIuZ "bytes\n", stats->indexed_objects, stats->total_objects, stats->received_bytes); } diff --git a/examples/for-each-ref.c b/examples/for-each-ref.c index 3bc25fcf2..020dab474 100644 --- a/examples/for-each-ref.c +++ b/examples/for-each-ref.c @@ -1,49 +1,44 @@ #include -#include #include "common.h" static int show_ref(git_reference *ref, void *data) { - git_repository *repo = data; - git_reference *resolved = NULL; - char hex[GIT_OID_HEXSZ+1]; - const git_oid *oid; - git_object *obj; - - if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) - check_lg2(git_reference_resolve(&resolved, ref), - "Unable to resolve symbolic reference", - git_reference_name(ref)); - - oid = git_reference_target(resolved ? resolved : ref); - git_oid_fmt(hex, oid); - hex[GIT_OID_HEXSZ] = 0; - check_lg2(git_object_lookup(&obj, repo, oid, GIT_OBJECT_ANY), - "Unable to lookup object", hex); - - printf("%s %-6s\t%s\n", - hex, - git_object_type2string(git_object_type(obj)), - git_reference_name(ref)); - - if (resolved) - git_reference_free(resolved); - return 0; + git_repository *repo = data; + git_reference *resolved = NULL; + char hex[GIT_OID_HEXSZ+1]; + const git_oid *oid; + git_object *obj; + + if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) + check_lg2(git_reference_resolve(&resolved, ref), + "Unable to resolve symbolic reference", + git_reference_name(ref)); + + oid = git_reference_target(resolved ? resolved : ref); + git_oid_fmt(hex, oid); + hex[GIT_OID_HEXSZ] = 0; + check_lg2(git_object_lookup(&obj, repo, oid, GIT_OBJECT_ANY), + "Unable to lookup object", hex); + + printf("%s %-6s\t%s\n", + hex, + git_object_type2string(git_object_type(obj)), + git_reference_name(ref)); + + if (resolved) + git_reference_free(resolved); + return 0; } -int main(int argc, char **argv) +int lg2_for_each_ref(git_repository *repo, int argc, char **argv) { - git_repository *repo; - git_libgit2_init(); - - if (argc != 1 || argv[1] /* silence -Wunused-parameter */) - fatal("Sorry, no for-each-ref options supported yet", NULL); - - check_lg2(git_repository_open(&repo, "."), - "Could not open repository", NULL); - check_lg2(git_reference_foreach(repo, show_ref, repo), - "Could not iterate over references", NULL); - - git_libgit2_shutdown(); - return 0; + UNUSED(argv); + + if (argc != 1) + fatal("Sorry, no for-each-ref options supported yet", NULL); + + check_lg2(git_reference_foreach(repo, show_ref, repo), + "Could not iterate over references", NULL); + + return 0; } diff --git a/examples/general.c b/examples/general.c index e0f5b5ec5..ddc53c3e8 100644 --- a/examples/general.c +++ b/examples/general.c @@ -36,6 +36,8 @@ * [pg]: https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain */ +#include "common.h" + /** * ### Includes * @@ -43,9 +45,7 @@ * that you need. It should be the only thing you need to include in order * to compile properly and get all the libgit2 API. */ -#include -#include -#include +#include "git2.h" static void oid_parsing(git_oid *out); static void object_database(git_repository *repo, git_oid *oid); @@ -76,12 +76,11 @@ static void check_error(int error_code, const char *action) exit(1); } -int main (int argc, char** argv) +int lg2_general(git_repository *repo, int argc, char** argv) { int error; git_oid oid; char *repo_path; - git_repository *repo; /** * Initialize the library, this will set up any global state which libgit2 needs @@ -625,7 +624,7 @@ static void revwalking(git_repository *repo) static void index_walking(git_repository *repo) { git_index *index; - unsigned int i, ecount; + size_t i, ecount; printf("\n*Index Walking*\n"); diff --git a/examples/network/index-pack.c b/examples/index-pack.c similarity index 64% rename from examples/network/index-pack.c rename to examples/index-pack.c index 128c7ebf5..c58ac038a 100644 --- a/examples/network/index-pack.c +++ b/examples/index-pack.c @@ -1,40 +1,21 @@ -#include -#include -#include -#include -#include -#include -#include -#ifdef _WIN32 -# include -# include - -# define open _open -# define read _read -# define close _close - -#define ssize_t unsigned int -#else -# include -#endif #include "common.h" /* * This could be run in the main loop whilst the application waits for * the indexing to finish in a worker thread */ -static int index_cb(const git_transfer_progress *stats, void *data) +static int index_cb(const git_indexer_progress *stats, void *data) { (void)data; - printf("\rProcessing %d of %d", stats->indexed_objects, stats->total_objects); + printf("\rProcessing %u of %u", stats->indexed_objects, stats->total_objects); return 0; } -int index_pack(git_repository *repo, int argc, char **argv) +int lg2_index_pack(git_repository *repo, int argc, char **argv) { git_indexer *idx; - git_transfer_progress stats = {0, 0}; + git_indexer_progress stats = {0, 0}; int error; char hash[GIT_OID_HEXSZ + 1] = {0}; int fd; @@ -78,7 +59,7 @@ int index_pack(git_repository *repo, int argc, char **argv) if ((error = git_indexer_commit(idx, &stats)) < 0) goto cleanup; - printf("\rIndexing %d of %d\n", stats.indexed_objects, stats.total_objects); + printf("\rIndexing %u of %u\n", stats.indexed_objects, stats.total_objects); git_oid_fmt(hash, git_indexer_hash(idx)); puts(hash); diff --git a/examples/init.c b/examples/init.c index fe7a67224..2f681c5ae 100644 --- a/examples/init.c +++ b/examples/init.c @@ -27,7 +27,7 @@ */ /** Forward declarations of helpers */ -struct opts { +struct init_opts { int no_options; int quiet; int bare; @@ -38,15 +38,11 @@ struct opts { const char *dir; }; static void create_initial_commit(git_repository *repo); -static void parse_opts(struct opts *o, int argc, char *argv[]); +static void parse_opts(struct init_opts *o, int argc, char *argv[]); - -int main(int argc, char *argv[]) +int lg2_init(git_repository *repo, int argc, char *argv[]) { - git_repository *repo = NULL; - struct opts o = { 1, 0, 0, 0, GIT_REPOSITORY_INIT_SHARED_UMASK, 0, 0, 0 }; - - git_libgit2_init(); + struct init_opts o = { 1, 0, 0, 0, GIT_REPOSITORY_INIT_SHARED_UMASK, 0, 0, 0 }; parse_opts(&o, argc, argv); @@ -116,7 +112,6 @@ int main(int argc, char *argv[]) } git_repository_free(repo); - git_libgit2_shutdown(); return 0; } @@ -215,7 +210,7 @@ static uint32_t parse_shared(const char *shared) return 0; } -static void parse_opts(struct opts *o, int argc, char *argv[]) +static void parse_opts(struct init_opts *o, int argc, char *argv[]) { struct args_info args = ARGS_INFO_INIT; const char *sharedarg; @@ -249,5 +244,5 @@ static void parse_opts(struct opts *o, int argc, char *argv[]) } if (!o->dir) - usage("must specify directory to init", NULL); + usage("must specify directory to init", ""); } diff --git a/examples/lg2.c b/examples/lg2.c new file mode 100644 index 000000000..a3987c34d --- /dev/null +++ b/examples/lg2.c @@ -0,0 +1,122 @@ +#include "common.h" + +/* This part is not strictly libgit2-dependent, but you can use this + * as a starting point for a git-like tool */ + +typedef int (*git_command_fn)(git_repository *, int , char **); + +struct { + char *name; + git_command_fn fn; + char requires_repo; +} commands[] = { + { "add", lg2_add, 1 }, + { "blame", lg2_blame, 1 }, + { "cat-file", lg2_cat_file, 1 }, + { "checkout", lg2_checkout, 1 }, + { "clone", lg2_clone, 0 }, + { "config", lg2_config, 1 }, + { "describe", lg2_describe, 1 }, + { "diff", lg2_diff, 1 }, + { "fetch", lg2_fetch, 1 }, + { "for-each-ref", lg2_for_each_ref, 1 }, + { "general", lg2_general, 0 }, + { "index-pack", lg2_index_pack, 1 }, + { "init", lg2_init, 0 }, + { "log", lg2_log, 1 }, + { "ls-files", lg2_ls_files, 1 }, + { "ls-remote", lg2_ls_remote, 1 }, + { "merge", lg2_merge, 1 }, + { "remote", lg2_remote, 1 }, + { "rev-list", lg2_rev_list, 1 }, + { "rev-parse", lg2_rev_parse, 1 }, + { "show-index", lg2_show_index, 0 }, + { "stash", lg2_stash, 1 }, + { "status", lg2_status, 1 }, + { "tag", lg2_tag, 1 }, +}; + +static int run_command(git_command_fn fn, git_repository *repo, struct args_info args) +{ + int error; + + /* Run the command. If something goes wrong, print the error message to stderr */ + error = fn(repo, args.argc - args.pos, &args.argv[args.pos]); + if (error < 0) { + if (git_error_last() == NULL) + fprintf(stderr, "Error without message"); + else + fprintf(stderr, "Bad news:\n %s\n", git_error_last()->message); + } + + return !!error; +} + +static int usage(const char *prog) +{ + size_t i; + + fprintf(stderr, "usage: %s ...\n\nAvailable commands:\n\n", prog); + for (i = 0; i < ARRAY_SIZE(commands); i++) + fprintf(stderr, "\t%s\n", commands[i].name); + + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + struct args_info args = ARGS_INFO_INIT; + git_repository *repo = NULL; + const char *git_dir = NULL; + int return_code = 1; + size_t i; + + if (argc < 2) + usage(argv[0]); + + git_libgit2_init(); + + for (args.pos = 1; args.pos < args.argc; ++args.pos) { + char *a = args.argv[args.pos]; + + if (a[0] != '-') { + /* non-arg */ + break; + } else if (optional_str_arg(&git_dir, &args, "--git-dir", ".git")) { + continue; + } else if (match_arg_separator(&args)) { + break; + } + } + + if (args.pos == args.argc) + usage(argv[0]); + + if (!git_dir) + git_dir = "."; + + for (i = 0; i < ARRAY_SIZE(commands); ++i) { + if (strcmp(args.argv[args.pos], commands[i].name)) + continue; + + /* + * Before running the actual command, create an instance + * of the local repository and pass it to the function. + * */ + if (commands[i].requires_repo) { + check_lg2(git_repository_open_ext(&repo, git_dir, 0, NULL), + "Unable to open repository '%s'", git_dir); + } + + return_code = run_command(commands[i].fn, repo, args); + goto shutdown; + } + + fprintf(stderr, "Command not found: %s\n", argv[1]); + +shutdown: + git_repository_free(repo); + git_libgit2_shutdown(); + + return return_code; +} diff --git a/examples/log.c b/examples/log.c index 3e891e4d8..3eee7d421 100644 --- a/examples/log.c +++ b/examples/log.c @@ -71,7 +71,7 @@ static int match_with_parent(git_commit *commit, int i, git_diff_options *); static int signature_matches(const git_signature *sig, const char *filter); static int log_message_matches(const git_commit *commit, const char *filter); -int main(int argc, char *argv[]) +int lg2_log(git_repository *repo, int argc, char *argv[]) { int i, count = 0, printed = 0, parents, last_arg; struct log_state s; @@ -81,11 +81,9 @@ int main(int argc, char *argv[]) git_commit *commit = NULL; git_pathspec *ps = NULL; - git_libgit2_init(); - /** Parse arguments and set up revwalker. */ - last_arg = parse_options(&s, &opt, argc, argv); + s.repo = repo; diffopts.pathspec.strings = &argv[last_arg]; diffopts.pathspec.count = argc - last_arg; @@ -180,8 +178,6 @@ int main(int argc, char *argv[]) git_pathspec_free(ps); git_revwalk_free(s.walker); - git_repository_free(s.repo); - git_libgit2_shutdown(); return 0; } @@ -243,13 +239,6 @@ static int add_revision(struct log_state *s, const char *revstr) git_revspec revs; int hide = 0; - /** Open repo on demand if it isn't already open. */ - if (!s->repo) { - if (!s->repodir) s->repodir = "."; - check_lg2(git_repository_open_ext(&s->repo, s->repodir, 0, NULL), - "Could not open repository", s->repodir); - } - if (!revstr) { push_rev(s, NULL, hide); return 0; @@ -435,8 +424,7 @@ static int parse_options( else /** Try failed revision parse as filename. */ break; - } else if (!strcmp(a, "--")) { - ++args.pos; + } else if (!match_arg_separator(&args)) { break; } else if (!strcmp(a, "--date-order")) diff --git a/examples/ls-files.c b/examples/ls-files.c index 98c89c93c..a23506962 100644 --- a/examples/ls-files.c +++ b/examples/ls-files.c @@ -13,7 +13,6 @@ */ #include "common.h" -#include "array.h" /** * This example demonstrates the libgit2 index APIs to roughly @@ -26,11 +25,11 @@ * This currently supports the default behavior and the `--error-unmatch` option. */ -typedef struct { +struct ls_options { int error_unmatch; char *files[1024]; size_t file_count; -} ls_options; +}; static void usage(const char *message, const char *arg) { @@ -42,12 +41,12 @@ static void usage(const char *message, const char *arg) exit(1); } -static int parse_options(ls_options *opts, int argc, char *argv[]) +static int parse_options(struct ls_options *opts, int argc, char *argv[]) { int parsing_files = 0; int i; - memset(opts, 0, sizeof(ls_options)); + memset(opts, 0, sizeof(struct ls_options)); if (argc < 2) return 0; @@ -79,7 +78,7 @@ static int parse_options(ls_options *opts, int argc, char *argv[]) return 0; } -static int print_paths(ls_options *opts, git_index *index) +static int print_paths(struct ls_options *opts, git_index *index) { size_t i; const git_index_entry *entry; @@ -111,21 +110,15 @@ static int print_paths(ls_options *opts, git_index *index) return 0; } -int main(int argc, char *argv[]) +int lg2_ls_files(git_repository *repo, int argc, char *argv[]) { - ls_options opts; - git_repository *repo = NULL; git_index *index = NULL; + struct ls_options opts; int error; if ((error = parse_options(&opts, argc, argv)) < 0) return error; - git_libgit2_init(); - - if ((error = git_repository_open_ext(&repo, ".", 0, NULL)) < 0) - goto cleanup; - if ((error = git_repository_index(&index, repo)) < 0) goto cleanup; @@ -133,8 +126,6 @@ int main(int argc, char *argv[]) cleanup: git_index_free(index); - git_repository_free(repo); - git_libgit2_shutdown(); return error; } diff --git a/examples/network/ls-remote.c b/examples/ls-remote.c similarity index 90% rename from examples/network/ls-remote.c rename to examples/ls-remote.c index fb258acbe..03ed887d1 100644 --- a/examples/network/ls-remote.c +++ b/examples/ls-remote.c @@ -1,7 +1,3 @@ -#include -#include -#include -#include #include "common.h" static int use_remote(git_repository *repo, char *name) @@ -49,7 +45,7 @@ cleanup: } /** Entry point for this command */ -int ls_remote(git_repository *repo, int argc, char **argv) +int lg2_ls_remote(git_repository *repo, int argc, char **argv) { int error; diff --git a/examples/merge.c b/examples/merge.c index 13e3f9632..460c06a25 100644 --- a/examples/merge.c +++ b/examples/merge.c @@ -13,11 +13,6 @@ */ #include "common.h" -#include - -#ifdef _MSC_VER -#define snprintf sprintf_s -#endif /** The following example demonstrates how to do merges with libgit2. * @@ -28,7 +23,7 @@ * */ -typedef struct { +struct merge_options { const char **heads; size_t heads_count; @@ -36,7 +31,7 @@ typedef struct { size_t annotated_count; int no_commit : 1; -} merge_options; +}; static void print_usage(void) { @@ -44,7 +39,7 @@ static void print_usage(void) exit(1); } -static void merge_options_init(merge_options *opts) +static void merge_options_init(struct merge_options *opts) { memset(opts, 0, sizeof(*opts)); @@ -54,18 +49,18 @@ static void merge_options_init(merge_options *opts) opts->annotated_count = 0; } -static void opts_add_refish(merge_options *opts, const char *refish) +static void opts_add_refish(struct merge_options *opts, const char *refish) { size_t sz; assert(opts != NULL); sz = ++opts->heads_count * sizeof(opts->heads[0]); - opts->heads = xrealloc(opts->heads, sz); + opts->heads = xrealloc((void *) opts->heads, sz); opts->heads[opts->heads_count - 1] = refish; } -static void parse_options(const char **repo_path, merge_options *opts, int argc, char **argv) +static void parse_options(const char **repo_path, struct merge_options *opts, int argc, char **argv) { struct args_info args = ARGS_INFO_INIT; @@ -87,7 +82,7 @@ static void parse_options(const char **repo_path, merge_options *opts, int argc, } } -static int resolve_heads(git_repository *repo, merge_options *opts) +static int resolve_heads(git_repository *repo, struct merge_options *opts) { git_annotated_commit **annotated = calloc(opts->heads_count, sizeof(git_annotated_commit *)); size_t annotated_count = 0, i; @@ -205,7 +200,7 @@ static void output_conflicts(git_index *index) git_index_conflict_iterator_free(conflicts); } -static int create_merge_commit(git_repository *repo, git_index *index, merge_options *opts) +static int create_merge_commit(git_repository *repo, git_index *index, struct merge_options *opts) { git_oid tree_oid, commit_oid; git_tree *tree; @@ -224,6 +219,7 @@ static int create_merge_commit(git_repository *repo, git_index *index, merge_opt check_lg2(git_repository_head(&head_ref, repo), "failed to get repo HEAD", NULL); if (resolve_refish(&merge_commit, repo, opts->heads[0])) { fprintf(stderr, "failed to resolve refish %s", opts->heads[0]); + free(parents); return -1; } @@ -278,10 +274,9 @@ cleanup: return err; } -int main(int argc, char **argv) +int lg2_merge(git_repository *repo, int argc, char **argv) { - git_repository *repo = NULL; - merge_options opts; + struct merge_options opts; git_index *index; git_repository_state_t state; git_merge_analysis_t analysis; @@ -292,11 +287,6 @@ int main(int argc, char **argv) merge_options_init(&opts); parse_options(&path, &opts, argc, argv); - git_libgit2_init(); - - check_lg2(git_repository_open_ext(&repo, path, 0, NULL), - "Could not open repository", NULL); - state = git_repository_state(repo); if (state != GIT_REPOSITORY_STATE_NONE) { fprintf(stderr, "repository is in unexpected state %d\n", state); @@ -364,10 +354,8 @@ int main(int argc, char **argv) } cleanup: - free(opts.heads); + free((char **)opts.heads); free(opts.annotated); - git_repository_free(repo); - git_libgit2_shutdown(); return 0; } diff --git a/examples/network/.gitignore b/examples/network/.gitignore deleted file mode 100644 index 1b48e66ed..000000000 --- a/examples/network/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/git2 diff --git a/examples/network/Makefile b/examples/network/Makefile deleted file mode 100644 index f65c6cb26..000000000 --- a/examples/network/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -default: all - -CC = gcc -CFLAGS += -g -CFLAGS += -I../../include -LDFLAGS += -L../../build -L../.. -LIBRARIES += -lgit2 -lpthread - -OBJECTS = \ - git2.o \ - ls-remote.o \ - fetch.o \ - clone.o \ - index-pack.o \ - common.o - -all: $(OBJECTS) - $(CC) $(CFLAGS) $(LDFLAGS) -o git2 $(OBJECTS) $(LIBRARIES) - -clean: - $(RM) $(OBJECTS) - $(RM) git2 diff --git a/examples/network/common.c b/examples/network/common.c deleted file mode 100644 index b0afb0238..000000000 --- a/examples/network/common.c +++ /dev/null @@ -1,85 +0,0 @@ -#include "common.h" -#include -#include -#include - -/* Shamelessly borrowed from http://stackoverflow.com/questions/3417837/ - * with permission of the original author, Martin Pool. - * http://sourcefrog.net/weblog/software/languages/C/unused.html - */ -#ifdef UNUSED -#elif defined(__GNUC__) -# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) -#elif defined(__LCLINT__) -# define UNUSED(x) /*@unused@*/ x -#else -# define UNUSED(x) x -#endif - -static int readline(char **out) -{ - int c, error = 0, length = 0, allocated = 0; - char *line = NULL; - - errno = 0; - - while ((c = getchar()) != EOF) { - if (length == allocated) { - allocated += 16; - - if ((line = realloc(line, allocated)) == NULL) { - error = -1; - goto error; - } - } - - if (c == '\n') - break; - - line[length++] = c; - } - - if (errno != 0) { - error = -1; - goto error; - } - - line[length] = '\0'; - *out = line; - line = NULL; - error = length; -error: - free(line); - return error; -} - -int cred_acquire_cb(git_cred **out, - const char * UNUSED(url), - const char * UNUSED(username_from_url), - unsigned int UNUSED(allowed_types), - void * UNUSED(payload)) -{ - char *username = NULL, *password = NULL; - int error; - - printf("Username: "); - if (readline(&username) < 0) { - fprintf(stderr, "Unable to read username: %s", strerror(errno)); - return -1; - } - - /* Yup. Right there on your terminal. Careful where you copy/paste output. */ - printf("Password: "); - if (readline(&password) < 0) { - fprintf(stderr, "Unable to read password: %s", strerror(errno)); - free(username); - return -1; - } - - error = git_cred_userpass_plaintext_new(out, username, password); - - free(username); - free(password); - - return error; -} diff --git a/examples/network/common.h b/examples/network/common.h deleted file mode 100644 index 1b09caad4..000000000 --- a/examples/network/common.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef __COMMON_H__ -#define __COMMON_H__ - -#include - -typedef int (*git_cb)(git_repository *, int , char **); - -int ls_remote(git_repository *repo, int argc, char **argv); -int parse_pkt_line(git_repository *repo, int argc, char **argv); -int show_remote(git_repository *repo, int argc, char **argv); -int fetch(git_repository *repo, int argc, char **argv); -int index_pack(git_repository *repo, int argc, char **argv); -int do_clone(git_repository *repo, int argc, char **argv); - -int cred_acquire_cb(git_cred **out, - const char * url, - const char * username_from_url, - unsigned int allowed_types, - void *payload); - -#ifndef PRIuZ -/* Define the printf format specifer to use for size_t output */ -#if defined(_MSC_VER) || defined(__MINGW32__) -# define PRIuZ "Iu" -#else -# define PRIuZ "zu" -#endif -#endif - -#endif /* __COMMON_H__ */ diff --git a/examples/network/git2.c b/examples/network/git2.c deleted file mode 100644 index d0d0eb610..000000000 --- a/examples/network/git2.c +++ /dev/null @@ -1,90 +0,0 @@ -#include -#include -#include - -#include "../common.h" -#include "common.h" - -/* This part is not strictly libgit2-dependent, but you can use this - * as a starting point for a git-like tool */ - -struct { - char *name; - git_cb fn; -} commands[] = { - {"ls-remote", ls_remote}, - {"fetch", fetch}, - {"clone", do_clone}, - {"index-pack", index_pack}, - { NULL, NULL} -}; - -static int run_command(git_cb fn, git_repository *repo, struct args_info args) -{ - int error; - - /* Run the command. If something goes wrong, print the error message to stderr */ - error = fn(repo, args.argc - args.pos, &args.argv[args.pos]); - if (error < 0) { - if (git_error_last() == NULL) - fprintf(stderr, "Error without message"); - else - fprintf(stderr, "Bad news:\n %s\n", git_error_last()->message); - } - - return !!error; -} - -int main(int argc, char **argv) -{ - int i; - int return_code = 1; - int error; - git_repository *repo; - struct args_info args = ARGS_INFO_INIT; - const char *git_dir = NULL; - - if (argc < 2) { - fprintf(stderr, "usage: %s [repo]\n", argv[0]); - exit(EXIT_FAILURE); - } - - git_libgit2_init(); - - for (args.pos = 1; args.pos < args.argc; ++args.pos) { - char *a = args.argv[args.pos]; - - if (a[0] != '-') { - /* non-arg */ - break; - } else if (optional_str_arg(&git_dir, &args, "--git-dir", ".git")) { - continue; - } else if (!strcmp(a, "--")) { - /* arg separator */ - break; - } - } - - /* Before running the actual command, create an instance of the local - * repository and pass it to the function. */ - - error = git_repository_open(&repo, git_dir); - if (error < 0) - repo = NULL; - - for (i = 0; commands[i].name != NULL; ++i) { - if (!strcmp(args.argv[args.pos], commands[i].name)) { - return_code = run_command(commands[i].fn, repo, args); - goto shutdown; - } - } - - fprintf(stderr, "Command not found: %s\n", argv[1]); - -shutdown: - git_repository_free(repo); - - git_libgit2_shutdown(); - - return return_code; -} diff --git a/examples/remote.c b/examples/remote.c index 1cb61ccd8..34d886526 100644 --- a/examples/remote.c +++ b/examples/remote.c @@ -30,7 +30,7 @@ enum subcmd { subcmd_show, }; -struct opts { +struct remote_opts { enum subcmd cmd; /* for command-specific args */ @@ -38,34 +38,23 @@ struct opts { char **argv; }; -static int cmd_add(git_repository *repo, struct opts *o); -static int cmd_remove(git_repository *repo, struct opts *o); -static int cmd_rename(git_repository *repo, struct opts *o); -static int cmd_seturl(git_repository *repo, struct opts *o); -static int cmd_show(git_repository *repo, struct opts *o); +static int cmd_add(git_repository *repo, struct remote_opts *o); +static int cmd_remove(git_repository *repo, struct remote_opts *o); +static int cmd_rename(git_repository *repo, struct remote_opts *o); +static int cmd_seturl(git_repository *repo, struct remote_opts *o); +static int cmd_show(git_repository *repo, struct remote_opts *o); static void parse_subcmd( - struct opts *opt, int argc, char **argv); + struct remote_opts *opt, int argc, char **argv); static void usage(const char *msg, const char *arg); -int main(int argc, char *argv[]) +int lg2_remote(git_repository *repo, int argc, char *argv[]) { int retval = 0; - struct opts opt = {0}; - git_buf buf = GIT_BUF_INIT_CONST(NULL, 0); - git_repository *repo = NULL; + struct remote_opts opt = {0}; parse_subcmd(&opt, argc, argv); - git_libgit2_init(); - - check_lg2(git_repository_discover(&buf, ".", 0, NULL), - "Could not find repository", NULL); - - check_lg2(git_repository_open(&repo, buf.ptr), - "Could not open repository", NULL); - git_buf_dispose(&buf); - switch (opt.cmd) { case subcmd_add: @@ -85,12 +74,10 @@ int main(int argc, char *argv[]) break; } - git_libgit2_shutdown(); - return retval; } -static int cmd_add(git_repository *repo, struct opts *o) +static int cmd_add(git_repository *repo, struct remote_opts *o) { char *name, *url; git_remote *remote = {0}; @@ -107,7 +94,7 @@ static int cmd_add(git_repository *repo, struct opts *o) return 0; } -static int cmd_remove(git_repository *repo, struct opts *o) +static int cmd_remove(git_repository *repo, struct remote_opts *o) { char *name; @@ -122,7 +109,7 @@ static int cmd_remove(git_repository *repo, struct opts *o) return 0; } -static int cmd_rename(git_repository *repo, struct opts *o) +static int cmd_rename(git_repository *repo, struct remote_opts *o) { int i, retval; char *old, *new; @@ -147,7 +134,7 @@ static int cmd_rename(git_repository *repo, struct opts *o) return retval; } -static int cmd_seturl(git_repository *repo, struct opts *o) +static int cmd_seturl(git_repository *repo, struct remote_opts *o) { int i, retval, push = 0; char *name = NULL, *url = NULL; @@ -179,7 +166,7 @@ static int cmd_seturl(git_repository *repo, struct opts *o) return 0; } -static int cmd_show(git_repository *repo, struct opts *o) +static int cmd_show(git_repository *repo, struct remote_opts *o) { int i; const char *arg, *name, *fetch, *push; @@ -226,7 +213,7 @@ static int cmd_show(git_repository *repo, struct opts *o) } static void parse_subcmd( - struct opts *opt, int argc, char **argv) + struct remote_opts *opt, int argc, char **argv) { char *arg = argv[1]; enum subcmd cmd = 0; diff --git a/examples/rev-list.c b/examples/rev-list.c index ee9afc441..75fb19e70 100644 --- a/examples/rev-list.c +++ b/examples/rev-list.c @@ -15,20 +15,24 @@ #include "common.h" -static int revwalk_parseopts(git_repository *repo, git_revwalk *walk, int nopts, char **opts); +#include -int main (int argc, char **argv) +static int revwalk_parse_options(git_sort_t *sort, struct args_info *args); +static int revwalk_parse_revs(git_repository *repo, git_revwalk *walk, struct args_info *args); + +int lg2_rev_list(git_repository *repo, int argc, char **argv) { - git_repository *repo; + struct args_info args = ARGS_INFO_INIT; git_revwalk *walk; git_oid oid; + git_sort_t sort; char buf[GIT_OID_HEXSZ+1]; - git_libgit2_init(); + check_lg2(revwalk_parse_options(&sort, &args), "parsing options", NULL); - check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), "opening repository", NULL); check_lg2(git_revwalk_new(&walk, repo), "allocating revwalk", NULL); - check_lg2(revwalk_parseopts(repo, walk, argc-1, argv+1), "parsing options", NULL); + git_revwalk_sorting(walk, sort); + check_lg2(revwalk_parse_revs(repo, walk, &args), "parsing revs", NULL); while (!git_revwalk_next(&oid, walk)) { git_oid_fmt(buf, &oid); @@ -36,7 +40,7 @@ int main (int argc, char **argv) printf("%s\n", buf); } - git_libgit2_shutdown(); + git_revwalk_free(walk); return 0; } @@ -85,33 +89,60 @@ out: return error; } -static int revwalk_parseopts(git_repository *repo, git_revwalk *walk, int nopts, char **opts) +static void print_usage(void) { - int hide, i, error; - unsigned int sorting = GIT_SORT_NONE; + fprintf(stderr, "rev-list [--git-dir=dir] [--topo-order|--date-order] [--reverse] \n"); + exit(-1); +} + +static int revwalk_parse_options(git_sort_t *sort, struct args_info *args) +{ + assert(sort && args); + *sort = GIT_SORT_NONE; + + if (args->argc < 1) + print_usage(); + + for (args->pos = 1; args->pos < args->argc; ++args->pos) { + const char *curr = args->argv[args->pos]; + + if (!strcmp(curr, "--topo-order")) { + *sort |= GIT_SORT_TOPOLOGICAL; + } else if (!strcmp(curr, "--date-order")) { + *sort |= GIT_SORT_TIME; + } else if (!strcmp(curr, "--reverse")) { + *sort |= (*sort & ~GIT_SORT_REVERSE) ^ GIT_SORT_REVERSE; + } else { + break; + } + } + return 0; +} + +static int revwalk_parse_revs(git_repository *repo, git_revwalk *walk, struct args_info *args) +{ + int hide, error; + git_oid oid; hide = 0; - for (i = 0; i < nopts; i++) { - if (!strcmp(opts[i], "--topo-order")) { - sorting = GIT_SORT_TOPOLOGICAL | (sorting & GIT_SORT_REVERSE); - git_revwalk_sorting(walk, sorting); - } else if (!strcmp(opts[i], "--date-order")) { - sorting = GIT_SORT_TIME | (sorting & GIT_SORT_REVERSE); - git_revwalk_sorting(walk, sorting); - } else if (!strcmp(opts[i], "--reverse")) { - sorting = (sorting & ~GIT_SORT_REVERSE) - | ((sorting & GIT_SORT_REVERSE) ? 0 : GIT_SORT_REVERSE); - git_revwalk_sorting(walk, sorting); - } else if (!strcmp(opts[i], "--not")) { + for (; args->pos < args->argc; ++args->pos) { + const char *curr = args->argv[args->pos]; + + if (!strcmp(curr, "--not")) { hide = !hide; - } else if (opts[i][0] == '^') { - if ((error = push_spec(repo, walk, opts[i] + 1, !hide))) + } else if (curr[0] == '^') { + if ((error = push_spec(repo, walk, curr + 1, !hide))) return error; - } else if (strstr(opts[i], "..")) { - if ((error = push_range(repo, walk, opts[i], hide))) + } else if (strstr(curr, "..")) { + if ((error = push_range(repo, walk, curr, hide))) return error; } else { - if ((error = push_spec(repo, walk, opts[i], hide))) + if (push_spec(repo, walk, curr, hide) == 0) + continue; + + if ((error = git_oid_fromstr(&oid, curr))) + return error; + if ((error = push_commit(walk, &oid, hide))) return error; } } diff --git a/examples/rev-parse.c b/examples/rev-parse.c index 483d6e019..7d6e9986f 100644 --- a/examples/rev-parse.c +++ b/examples/rev-parse.c @@ -16,26 +16,20 @@ /** Forward declarations for helpers. */ struct parse_state { - git_repository *repo; const char *repodir; const char *spec; int not; }; static void parse_opts(struct parse_state *ps, int argc, char *argv[]); -static int parse_revision(struct parse_state *ps); +static int parse_revision(git_repository *repo, struct parse_state *ps); - -int main(int argc, char *argv[]) +int lg2_rev_parse(git_repository *repo, int argc, char *argv[]) { struct parse_state ps = {0}; - git_libgit2_init(); parse_opts(&ps, argc, argv); - check_lg2(parse_revision(&ps), "Parsing", NULL); - - git_repository_free(ps.repo); - git_libgit2_shutdown(); + check_lg2(parse_revision(repo, &ps), "Parsing", NULL); return 0; } @@ -68,19 +62,12 @@ static void parse_opts(struct parse_state *ps, int argc, char *argv[]) } } -static int parse_revision(struct parse_state *ps) +static int parse_revision(git_repository *repo, struct parse_state *ps) { git_revspec rs; char str[GIT_OID_HEXSZ + 1]; - if (!ps->repo) { - if (!ps->repodir) - ps->repodir = "."; - check_lg2(git_repository_open_ext(&ps->repo, ps->repodir, 0, NULL), - "Could not open repository from", ps->repodir); - } - - check_lg2(git_revparse(&rs, ps->repo, ps->spec), "Could not parse", ps->spec); + check_lg2(git_revparse(&rs, repo, ps->spec), "Could not parse", ps->spec); if ((rs.flags & GIT_REVPARSE_SINGLE) != 0) { git_oid_tostr(str, sizeof(str), git_object_id(rs.from)); @@ -94,7 +81,7 @@ static int parse_revision(struct parse_state *ps) if ((rs.flags & GIT_REVPARSE_MERGE_BASE) != 0) { git_oid base; - check_lg2(git_merge_base(&base, ps->repo, + check_lg2(git_merge_base(&base, repo, git_object_id(rs.from), git_object_id(rs.to)), "Could not find merge base", ps->spec); diff --git a/examples/showindex.c b/examples/show-index.c similarity index 93% rename from examples/showindex.c rename to examples/show-index.c index 43be5e24c..7aaa45e65 100644 --- a/examples/showindex.c +++ b/examples/show-index.c @@ -14,17 +14,15 @@ #include "common.h" -int main (int argc, char** argv) +int lg2_show_index(git_repository *repo, int argc, char **argv) { git_index *index; - unsigned int i, ecount; + size_t i, ecount; char *dir = "."; size_t dirlen; char out[GIT_OID_HEXSZ+1]; out[GIT_OID_HEXSZ] = '\0'; - git_libgit2_init(); - if (argc > 2) fatal("usage: showindex []", NULL); if (argc > 1) @@ -34,7 +32,6 @@ int main (int argc, char** argv) if (dirlen > 5 && strcmp(dir + dirlen - 5, "index") == 0) { check_lg2(git_index_open(&index, dir), "could not open index", dir); } else { - git_repository *repo; check_lg2(git_repository_open_ext(&repo, dir, 0, NULL), "could not open repository", dir); check_lg2(git_repository_index(&index, repo), "could not open repository index", NULL); git_repository_free(repo); @@ -64,7 +61,6 @@ int main (int argc, char** argv) } git_index_free(index); - git_libgit2_shutdown(); return 0; } diff --git a/examples/stash.c b/examples/stash.c new file mode 100644 index 000000000..8142439c7 --- /dev/null +++ b/examples/stash.c @@ -0,0 +1,157 @@ +/* + * libgit2 "stash" example - shows how to use the stash API + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include + +#include "common.h" + +enum subcmd { + SUBCMD_APPLY, + SUBCMD_LIST, + SUBCMD_POP, + SUBCMD_PUSH +}; + +struct opts { + enum subcmd cmd; + int argc; + char **argv; +}; + +static void usage(const char *fmt, ...) +{ + va_list ap; + + fputs("usage: git stash list\n", stderr); + fputs(" or: git stash ( pop | apply )\n", stderr); + fputs(" or: git stash [push]\n", stderr); + fputs("\n", stderr); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + exit(1); +} + +static void parse_subcommand(struct opts *opts, int argc, char *argv[]) +{ + char *arg = (argc < 2) ? "push" : argv[1]; + enum subcmd cmd; + + if (!strcmp(arg, "apply")) { + cmd = SUBCMD_APPLY; + } else if (!strcmp(arg, "list")) { + cmd = SUBCMD_LIST; + } else if (!strcmp(arg, "pop")) { + cmd = SUBCMD_POP; + } else if (!strcmp(arg, "push")) { + cmd = SUBCMD_PUSH; + } else { + usage("invalid command %s", arg); + return; + } + + opts->cmd = cmd; + opts->argc = (argc < 2) ? argc - 1 : argc - 2; + opts->argv = argv; +} + +static int cmd_apply(git_repository *repo, struct opts *opts) +{ + if (opts->argc) + usage("apply does not accept any parameters"); + + check_lg2(git_stash_apply(repo, 0, NULL), + "Unable to apply stash", NULL); + + return 0; +} + +static int list_stash_cb(size_t index, const char *message, + const git_oid *stash_id, void *payload) +{ + UNUSED(stash_id); + UNUSED(payload); + printf("stash@{%"PRIuZ"}: %s\n", index, message); + return 0; +} + +static int cmd_list(git_repository *repo, struct opts *opts) +{ + if (opts->argc) + usage("list does not accept any parameters"); + + check_lg2(git_stash_foreach(repo, list_stash_cb, NULL), + "Unable to list stashes", NULL); + + return 0; +} + +static int cmd_push(git_repository *repo, struct opts *opts) +{ + git_signature *signature; + git_commit *stash; + git_oid stashid; + + if (opts->argc) + usage("push does not accept any parameters"); + + check_lg2(git_signature_default(&signature, repo), + "Unable to get signature", NULL); + check_lg2(git_stash_save(&stashid, repo, signature, NULL, GIT_STASH_DEFAULT), + "Unable to save stash", NULL); + check_lg2(git_commit_lookup(&stash, repo, &stashid), + "Unable to lookup stash commit", NULL); + + printf("Saved working directory %s\n", git_commit_summary(stash)); + + git_signature_free(signature); + git_commit_free(stash); + + return 0; +} + +static int cmd_pop(git_repository *repo, struct opts *opts) +{ + if (opts->argc) + usage("pop does not accept any parameters"); + + check_lg2(git_stash_pop(repo, 0, NULL), + "Unable to pop stash", NULL); + + printf("Dropped refs/stash@{0}\n"); + + return 0; +} + +int lg2_stash(git_repository *repo, int argc, char *argv[]) +{ + struct opts opts = { 0 }; + + parse_subcommand(&opts, argc, argv); + + switch (opts.cmd) { + case SUBCMD_APPLY: + return cmd_apply(repo, &opts); + case SUBCMD_LIST: + return cmd_list(repo, &opts); + case SUBCMD_PUSH: + return cmd_push(repo, &opts); + case SUBCMD_POP: + return cmd_pop(repo, &opts); + } + + return -1; +} diff --git a/examples/status.c b/examples/status.c index 49f006dcc..8cf922127 100644 --- a/examples/status.c +++ b/examples/status.c @@ -13,12 +13,6 @@ */ #include "common.h" -#ifdef _WIN32 -# include -# define sleep(a) Sleep(a * 1000) -#else -# include -#endif /** * This example demonstrates the use of the libgit2 status APIs, @@ -49,7 +43,7 @@ enum { #define MAX_PATHSPEC 8 -struct opts { +struct status_opts { git_status_options statusopt; char *repodir; char *pathspec[MAX_PATHSPEC]; @@ -61,19 +55,16 @@ struct opts { int repeat; }; -static void parse_opts(struct opts *o, int argc, char *argv[]); +static void parse_opts(struct status_opts *o, int argc, char *argv[]); static void show_branch(git_repository *repo, int format); static void print_long(git_status_list *status); static void print_short(git_repository *repo, git_status_list *status); static int print_submod(git_submodule *sm, const char *name, void *payload); -int main(int argc, char *argv[]) +int lg2_status(git_repository *repo, int argc, char *argv[]) { - git_repository *repo = NULL; git_status_list *status; - struct opts o = { GIT_STATUS_OPTIONS_INIT, "." }; - - git_libgit2_init(); + struct status_opts o = { GIT_STATUS_OPTIONS_INIT, "." }; o.statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; o.statusopt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | @@ -82,13 +73,6 @@ int main(int argc, char *argv[]) parse_opts(&o, argc, argv); - /** - * Try to open the repository at the given path (or at the current - * directory if none was given). - */ - check_lg2(git_repository_open_ext(&repo, o.repodir, 0, NULL), - "Could not open repository", o.repodir); - if (git_repository_is_bare(repo)) fatal("Cannot report status on bare repository", git_repository_path(repo)); @@ -134,9 +118,6 @@ show_status: goto show_status; } - git_repository_free(repo); - git_libgit2_shutdown(); - return 0; } @@ -454,7 +435,7 @@ static int print_submod(git_submodule *sm, const char *name, void *payload) /** * Parse options that git's status command supports. */ -static void parse_opts(struct opts *o, int argc, char *argv[]) +static void parse_opts(struct status_opts *o, int argc, char *argv[]) { struct args_info args = ARGS_INFO_INIT; diff --git a/examples/tag.c b/examples/tag.c index fa405b8de..440829419 100644 --- a/examples/tag.c +++ b/examples/tag.c @@ -31,19 +31,19 @@ */ /** tag_options represents the parsed command line options */ -typedef struct { +struct tag_options { const char *message; const char *pattern; const char *tag_name; const char *target; int num_lines; int force; -} tag_options; +}; /** tag_state represents the current program state for dragging around */ typedef struct { git_repository *repo; - tag_options *opts; + struct tag_options *opts; } tag_state; /** An action to execute based on the command line arguments */ @@ -167,7 +167,7 @@ static void action_list_tags(tag_state *state) static void action_delete_tag(tag_state *state) { - tag_options *opts = state->opts; + struct tag_options *opts = state->opts; git_object *obj; git_buf abbrev_oid = {0}; @@ -191,7 +191,7 @@ static void action_delete_tag(tag_state *state) static void action_create_lighweight_tag(tag_state *state) { git_repository *repo = state->repo; - tag_options *opts = state->opts; + struct tag_options *opts = state->opts; git_oid oid; git_object *target; @@ -213,7 +213,7 @@ static void action_create_lighweight_tag(tag_state *state) static void action_create_tag(tag_state *state) { git_repository *repo = state->repo; - tag_options *opts = state->opts; + struct tag_options *opts = state->opts; git_signature *tagger; git_oid oid; git_object *target; @@ -243,7 +243,7 @@ static void print_usage(void) } /** Parse command line arguments and choose action to run when done */ -static void parse_options(tag_action *action, tag_options *opts, int argc, char **argv) +static void parse_options(tag_action *action, struct tag_options *opts, int argc, char **argv) { args_info args = ARGS_INFO_INIT; *action = &action_list_tags; @@ -281,7 +281,7 @@ static void parse_options(tag_action *action, tag_options *opts, int argc, char } /** Initialize tag_options struct */ -static void tag_options_init(tag_options *opts) +static void tag_options_init(struct tag_options *opts) { memset(opts, 0, sizeof(*opts)); @@ -293,18 +293,12 @@ static void tag_options_init(tag_options *opts) opts->force = 0; } -int main(int argc, char **argv) +int lg2_tag(git_repository *repo, int argc, char **argv) { - git_repository *repo; - tag_options opts; + struct tag_options opts; tag_action action; tag_state state; - git_libgit2_init(); - - check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), - "Could not open repository", NULL); - tag_options_init(&opts); parse_options(&action, &opts, argc, argv); @@ -312,8 +306,5 @@ int main(int argc, char **argv) state.opts = &opts; action(&state); - git_repository_free(repo); - git_libgit2_shutdown(); - return 0; } diff --git a/examples/test/test-rev-list.sh b/examples/test/test-rev-list.sh deleted file mode 100755 index aa645be5e..000000000 --- a/examples/test/test-rev-list.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/bash - -THIS_FILE="$(readlink -f "$0")" -ROOT="$(dirname "$(dirname "$(dirname "$THIS_FILE")")")" -PROGRAM="$ROOT"/examples/rev-list -LIBDIR="$ROOT"/build -REPO="$ROOT"/tests/resources/testrepo.git - -cd "$REPO" - -run () { - LD_LIBRARY_PATH="$LIBDIR" "$PROGRAM" "$@" -} - -diff -u - <(run --date-order a4a7dce) </dev/null || -a4a7dce85cf63874e984719f4fdd239f5145052f -c47800c7266a2be04c571c04d5a6614691ea99bd -9fd738e8f7967c078dceed8190330fc8648ee56a -4a202b346bb0fb0db7eff3cffeb3c70babbd2045 -5b5b025afb0b4c913b4c338a42934a3863bf3644 -8496071c1b46c854b31185ea97743be6a8774479 -EOF -diff -u - <(echo "$out") </dev/null || -8496071c1b46c854b31185ea97743be6a8774479 -5b5b025afb0b4c913b4c338a42934a3863bf3644 -4a202b346bb0fb0db7eff3cffeb3c70babbd2045 -9fd738e8f7967c078dceed8190330fc8648ee56a -c47800c7266a2be04c571c04d5a6614691ea99bd -a4a7dce85cf63874e984719f4fdd239f5145052f -EOF -diff -u - <(echo "$out") </dev/null || -a4a7dce85cf63874e984719f4fdd239f5145052f -c47800c7266a2be04c571c04d5a6614691ea99bd -9fd738e8f7967c078dceed8190330fc8648ee56a -4a202b346bb0fb0db7eff3cffeb3c70babbd2045 -5b5b025afb0b4c913b4c338a42934a3863bf3644 -8496071c1b46c854b31185ea97743be6a8774479 -EOF -diff -u - <(echo "$out") < +#include "git2.h" #include "config_backend.h" -#include -#include -#include -#include -#include - #define UNUSED(x) (void)(x) int foreach_cb(const git_config_entry *entry, void *payload) diff --git a/fuzzers/corpora/patch_parse/edit-file.diff b/fuzzers/corpora/patch_parse/edit-file.diff new file mode 100644 index 000000000..d9e783a7f --- /dev/null +++ b/fuzzers/corpora/patch_parse/edit-file.diff @@ -0,0 +1,13 @@ +diff --git a/fuzzers/patch_fuzzer.c b/fuzzers/patch_fuzzer.c +index 76186b6fb..f7ce73ac8 100644 +--- a/fuzzers/patch_fuzzer.c ++++ b/fuzzers/patch_fuzzer.c +@@ -32,7 +32,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) + git_patch* patch; + git_patch_options opts = {(uint32_t)data[0]}; + int status = git_patch_from_buffer(&patch, (const char*)data+1, size-1, &opts); +- if (status == 0 && patch) { ++ if (patch) { + git_patch_free(patch); + } + return 0; diff --git a/fuzzers/corpora/patch_parse/patch_fuzzer-patch.diff b/fuzzers/corpora/patch_parse/patch_fuzzer-patch.diff new file mode 100644 index 000000000..7c98d8ad4 --- /dev/null +++ b/fuzzers/corpora/patch_parse/patch_fuzzer-patch.diff @@ -0,0 +1,45 @@ +diff --git a/fuzzers/patch_fuzzer.c b/fuzzers/patch_fuzzer.c +new file mode 100644 +index 000000000..76186b6fb +--- /dev/null ++++ b/fuzzers/patch_fuzzer.c +@@ -0,0 +1,39 @@ ++/* ++ * libgit2 patch fuzzer target. ++ * ++ * Copyright (C) the libgit2 contributors. All rights reserved. ++ * ++ * This file is part of libgit2, distributed under the GNU GPL v2 with ++ * a Linking Exception. For full terms see the included COPYING file. ++ */ ++ ++#include "git2.h" ++#include "patch.h" ++#include "patch_parse.h" ++ ++#define UNUSED(x) (void)(x) ++ ++int LLVMFuzzerInitialize(int *argc, char ***argv) ++{ ++ UNUSED(argc); ++ UNUSED(argv); ++ ++ if (git_libgit2_init() < 0) ++ abort(); ++ ++ return 0; ++} ++ ++int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) ++{ ++ if (size < 1) { ++ return 0; ++ } ++ git_patch* patch; ++ git_patch_options opts = {(uint32_t)data[0]}; ++ int status = git_patch_from_buffer(&patch, (const char*)data+1, size-1, &opts); ++ if (status == 0 && patch) { ++ git_patch_free(patch); ++ } ++ return 0; ++} diff --git a/fuzzers/download_refs_fuzzer.c b/fuzzers/download_refs_fuzzer.c index 93f1b49b3..c5726cba1 100644 --- a/fuzzers/download_refs_fuzzer.c +++ b/fuzzers/download_refs_fuzzer.c @@ -7,14 +7,13 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include +#include #include #include -#include -#include #include "git2.h" #include "git2/sys/transport.h" +#include "futils.h" #define UNUSED(x) (void)(x) @@ -166,10 +165,23 @@ void fuzzer_git_abort(const char *op) int LLVMFuzzerInitialize(int *argc, char ***argv) { - char tmp[] = "/tmp/git2.XXXXXX"; +#if defined(_WIN32) + char tmpdir[MAX_PATH], path[MAX_PATH]; - UNUSED(argc); - UNUSED(argv); + if (GetTempPath((DWORD)sizeof(tmpdir), tmpdir) == 0) + abort(); + + if (GetTempFileName(tmpdir, "lg2", 1, path) == 0) + abort(); + + if (git_futils_mkdir(path, 0700, 0) < 0) + abort(); +#else + char path[] = "/tmp/git2.XXXXXX"; + + if (mkdtemp(path) != path) + abort(); +#endif if (git_libgit2_init() < 0) abort(); @@ -177,10 +189,10 @@ int LLVMFuzzerInitialize(int *argc, char ***argv) if (git_libgit2_opts(GIT_OPT_SET_PACK_MAX_OBJECTS, 10000000) < 0) abort(); - if (mkdtemp(tmp) != tmp) - abort(); + UNUSED(argc); + UNUSED(argv); - if (git_repository_init(&repo, tmp, 1) < 0) + if (git_repository_init(&repo, path, 1) < 0) fuzzer_git_abort("git_repository_init"); return 0; diff --git a/fuzzers/packfile_fuzzer.c b/fuzzers/packfile_fuzzer.c index a448233cb..50c115755 100644 --- a/fuzzers/packfile_fuzzer.c +++ b/fuzzers/packfile_fuzzer.c @@ -7,16 +7,12 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include -#include #include -#include -#include #include "git2.h" #include "git2/sys/mempack.h" - -#define UNUSED(x) (void)(x) +#include "common.h" +#include "buffer.h" static git_odb *odb = NULL; static git_odb_backend *mempack = NULL; @@ -27,8 +23,9 @@ static const unsigned int base_obj_len = 2; int LLVMFuzzerInitialize(int *argc, char ***argv) { - UNUSED(argc); - UNUSED(argv); + GIT_UNUSED(argc); + GIT_UNUSED(argv); + if (git_libgit2_init() < 0) { fprintf(stderr, "Failed to initialize libgit2\n"); abort(); @@ -54,12 +51,11 @@ int LLVMFuzzerInitialize(int *argc, char ***argv) int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + git_indexer_progress stats = {0, 0}; git_indexer *indexer = NULL; - git_transfer_progress stats = {0, 0}; + git_buf path = GIT_BUF_INIT; + git_oid oid; bool append_hash = false; - git_oid id; - char hash[GIT_OID_HEXSZ + 1] = {0}; - char path[PATH_MAX]; if (size == 0) return 0; @@ -70,7 +66,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) } git_mempack_reset(mempack); - if (git_odb_write(&id, odb, base_obj, base_obj_len, GIT_OBJECT_BLOB) < 0) { + if (git_odb_write(&oid, odb, base_obj, base_obj_len, GIT_OBJECT_BLOB) < 0) { fprintf(stderr, "Failed to add an object to the odb\n"); abort(); } @@ -92,7 +88,6 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) if (git_indexer_append(indexer, data, size, &stats) < 0) goto cleanup; if (append_hash) { - git_oid oid; if (git_odb_hash(&oid, data, size, GIT_OBJECT_BLOB) < 0) { fprintf(stderr, "Failed to compute the SHA1 hash\n"); abort(); @@ -104,19 +99,19 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) if (git_indexer_commit(indexer, &stats) < 0) goto cleanup; - /* - * We made it! We managed to produce a valid packfile. - * Let's clean it up. - */ - git_oid_fmt(hash, git_indexer_hash(indexer)); - printf("Generated packfile %s\n", hash); - snprintf(path, sizeof(path), "pack-%s.idx", hash); - unlink(path); - snprintf(path, sizeof(path), "pack-%s.pack", hash); - unlink(path); + if (git_buf_printf(&path, "pack-%s.idx", git_oid_tostr_s(git_indexer_hash(indexer))) < 0) + goto cleanup; + p_unlink(git_buf_cstr(&path)); + + git_buf_clear(&path); + + if (git_buf_printf(&path, "pack-%s.pack", git_oid_tostr_s(git_indexer_hash(indexer))) < 0) + goto cleanup; + p_unlink(git_buf_cstr(&path)); cleanup: git_mempack_reset(mempack); git_indexer_free(indexer); + git_buf_dispose(&path); return 0; } diff --git a/fuzzers/patch_parse_fuzzer.c b/fuzzers/patch_parse_fuzzer.c new file mode 100644 index 000000000..a9b02ad4d --- /dev/null +++ b/fuzzers/patch_parse_fuzzer.c @@ -0,0 +1,38 @@ +/* + * libgit2 patch parser fuzzer target. + * + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2.h" +#include "patch.h" +#include "patch_parse.h" + +#define UNUSED(x) (void)(x) + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + UNUSED(argc); + UNUSED(argv); + + if (git_libgit2_init() < 0) + abort(); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size) { + git_patch *patch = NULL; + git_patch_options opts = GIT_PATCH_OPTIONS_INIT; + opts.prefix_len = (uint32_t)data[0]; + git_patch_from_buffer(&patch, (const char *)data + 1, size - 1, + &opts); + git_patch_free(patch); + } + return 0; +} diff --git a/fuzzers/standalone_driver.c b/fuzzers/standalone_driver.c index 000bfbfa4..90e701333 100644 --- a/fuzzers/standalone_driver.c +++ b/fuzzers/standalone_driver.c @@ -5,14 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include -#include #include -#include -#include #include "git2.h" -#include "fileops.h" +#include "futils.h" #include "path.h" extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size); @@ -24,7 +20,7 @@ static int run_one_file(const char *filename) int error = 0; if (git_futils_readbuffer(&buf, filename) < 0) { - fprintf(stderr, "Failed to read %s: %m\n", filename); + fprintf(stderr, "Failed to read %s: %s\n", filename, git_error_last()->message); error = -1; goto exit; } @@ -57,7 +53,8 @@ int main(int argc, char **argv) LLVMFuzzerInitialize(&argc, &argv); if (git_path_dirload(&corpus_files, argv[1], 0, 0x0) < 0) { - fprintf(stderr, "Failed to scan corpus directory: %m\n"); + fprintf(stderr, "Failed to scan corpus directory '%s': %s\n", + argv[1], git_error_last()->message); error = -1; goto exit; } diff --git a/include/git2.h b/include/git2.h index c07c26038..f39d7fbe2 100644 --- a/include/git2.h +++ b/include/git2.h @@ -15,12 +15,14 @@ #include "git2/blame.h" #include "git2/branch.h" #include "git2/buffer.h" +#include "git2/cert.h" #include "git2/checkout.h" #include "git2/cherrypick.h" #include "git2/clone.h" #include "git2/commit.h" #include "git2/common.h" #include "git2/config.h" +#include "git2/credential.h" #include "git2/deprecated.h" #include "git2/describe.h" #include "git2/diff.h" diff --git a/include/git2/apply.h b/include/git2/apply.h index 91f93d75d..b248eaafe 100644 --- a/include/git2/apply.h +++ b/include/git2/apply.h @@ -53,25 +53,44 @@ typedef int GIT_CALLBACK(git_apply_hunk_cb)( const git_diff_hunk *hunk, void *payload); +/** Flags controlling the behavior of git_apply */ +typedef enum { + /** + * Don't actually make changes, just test that the patch applies. + * This is the equivalent of `git apply --check`. + */ + GIT_APPLY_CHECK = (1 << 0), +} git_apply_flags_t; + /** * Apply options structure * * Initialize with `GIT_APPLY_OPTIONS_INIT`. Alternatively, you can - * use `git_apply_init_options`. + * use `git_apply_options_init`. * * @see git_apply_to_tree, git_apply */ typedef struct { - unsigned int version; + unsigned int version; /**< The version */ + /** When applying a patch, callback that will be made per delta (file). */ git_apply_delta_cb delta_cb; + + /** When applying a patch, callback that will be made per hunk. */ git_apply_hunk_cb hunk_cb; + + /** Payload passed to both delta_cb & hunk_cb. */ void *payload; + + /** Bitmask of git_apply_flags_t */ + unsigned int flags; } git_apply_options; #define GIT_APPLY_OPTIONS_VERSION 1 #define GIT_APPLY_OPTIONS_INIT {GIT_APPLY_OPTIONS_VERSION} +GIT_EXTERN(int) git_apply_options_init(git_apply_options *opts, unsigned int version); + /** * Apply a `git_diff` to a `git_tree`, and return the resulting image * as an index. @@ -89,6 +108,7 @@ GIT_EXTERN(int) git_apply_to_tree( git_diff *diff, const git_apply_options *options); +/** Possible application locations for git_apply */ typedef enum { /** * Apply the patch to the workdir, leaving the index untouched. diff --git a/include/git2/attr.h b/include/git2/attr.h index 3752b9913..a3ab5a7a2 100644 --- a/include/git2/attr.h +++ b/include/git2/attr.h @@ -30,7 +30,7 @@ GIT_BEGIN_DECL * Then for file `xyz.c` looking up attribute "foo" gives a value for * which `GIT_ATTR_TRUE(value)` is true. */ -#define GIT_ATTR_TRUE(attr) (git_attr_value(attr) == GIT_ATTR_TRUE_T) +#define GIT_ATTR_IS_TRUE(attr) (git_attr_value(attr) == GIT_ATTR_VALUE_TRUE) /** * GIT_ATTR_FALSE checks if an attribute is set off. In core git @@ -44,7 +44,7 @@ GIT_BEGIN_DECL * Then for file `zyx.h` looking up attribute "foo" gives a value for * which `GIT_ATTR_FALSE(value)` is true. */ -#define GIT_ATTR_FALSE(attr) (git_attr_value(attr) == GIT_ATTR_FALSE_T) +#define GIT_ATTR_IS_FALSE(attr) (git_attr_value(attr) == GIT_ATTR_VALUE_FALSE) /** * GIT_ATTR_UNSPECIFIED checks if an attribute is unspecified. This @@ -62,7 +62,7 @@ GIT_BEGIN_DECL * file `onefile.rb` or looking up "bar" on any file will all give * `GIT_ATTR_UNSPECIFIED(value)` of true. */ -#define GIT_ATTR_UNSPECIFIED(attr) (git_attr_value(attr) == GIT_ATTR_UNSPECIFIED_T) +#define GIT_ATTR_IS_UNSPECIFIED(attr) (git_attr_value(attr) == GIT_ATTR_VALUE_UNSPECIFIED) /** * GIT_ATTR_HAS_VALUE checks if an attribute is set to a value (as @@ -74,17 +74,17 @@ GIT_BEGIN_DECL * Given this, looking up "eol" for `onefile.txt` will give back the * string "lf" and `GIT_ATTR_SET_TO_VALUE(attr)` will return true. */ -#define GIT_ATTR_HAS_VALUE(attr) (git_attr_value(attr) == GIT_ATTR_VALUE_T) +#define GIT_ATTR_HAS_VALUE(attr) (git_attr_value(attr) == GIT_ATTR_VALUE_STRING) /** * Possible states for an attribute */ typedef enum { - GIT_ATTR_UNSPECIFIED_T = 0, /**< The attribute has been left unspecified */ - GIT_ATTR_TRUE_T, /**< The attribute has been set */ - GIT_ATTR_FALSE_T, /**< The attribute has been unset */ - GIT_ATTR_VALUE_T, /**< This attribute has a value */ -} git_attr_t; + GIT_ATTR_VALUE_UNSPECIFIED = 0, /**< The attribute has been left unspecified */ + GIT_ATTR_VALUE_TRUE, /**< The attribute has been set */ + GIT_ATTR_VALUE_FALSE, /**< The attribute has been unset */ + GIT_ATTR_VALUE_STRING, /**< This attribute has a value */ +} git_attr_value_t; /** * Return the value type for a given attribute. @@ -99,7 +99,7 @@ typedef enum { * @param attr The attribute * @return the value type for the attribute */ -GIT_EXTERN(git_attr_t) git_attr_value(const char *attr); +GIT_EXTERN(git_attr_value_t) git_attr_value(const char *attr); /** * Check attribute flags: Reading values from index and working directory. @@ -119,13 +119,20 @@ GIT_EXTERN(git_attr_t) git_attr_value(const char *attr); #define GIT_ATTR_CHECK_INDEX_ONLY 2 /** - * Check attribute flags: Using the system attributes file. + * Check attribute flags: controlling extended attribute behavior. * * Normally, attribute checks include looking in the /etc (or system * equivalent) directory for a `gitattributes` file. Passing this * flag will cause attribute checks to ignore that file. + * equivalent) directory for a `gitattributes` file. Passing the + * `GIT_ATTR_CHECK_NO_SYSTEM` flag will cause attribute checks to + * ignore that file. + * + * Passing the `GIT_ATTR_CHECK_INCLUDE_HEAD` flag will use attributes + * from a `.gitattributes` file in the repository at the HEAD revision. */ -#define GIT_ATTR_CHECK_NO_SYSTEM (1 << 2) +#define GIT_ATTR_CHECK_NO_SYSTEM (1 << 2) +#define GIT_ATTR_CHECK_INCLUDE_HEAD (1 << 3) /** * Look up the value of one git attribute for path. @@ -231,8 +238,11 @@ GIT_EXTERN(int) git_attr_foreach( * disk no longer match the cached contents of memory. This will cause * the attributes files to be reloaded the next time that an attribute * access function is called. + * + * @param repo The repository containing the gitattributes cache + * @return 0 on success, or an error code */ -GIT_EXTERN(void) git_attr_cache_flush( +GIT_EXTERN(int) git_attr_cache_flush( git_repository *repo); /** diff --git a/include/git2/blame.h b/include/git2/blame.h index eef863f9c..73f6cf979 100644 --- a/include/git2/blame.h +++ b/include/git2/blame.h @@ -53,7 +53,7 @@ typedef enum { * Blame options structure * * Initialize with `GIT_BLAME_OPTIONS_INIT`. Alternatively, you can - * use `git_blame_init_options`. + * use `git_blame_options_init`. * */ typedef struct git_blame_options { @@ -100,7 +100,7 @@ typedef struct git_blame_options { * @param version The struct version; pass `GIT_BLAME_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_blame_init_options( +GIT_EXTERN(int) git_blame_options_init( git_blame_options *opts, unsigned int version); diff --git a/include/git2/blob.h b/include/git2/blob.h index ff1a4818f..7e2a745d1 100644 --- a/include/git2/blob.h +++ b/include/git2/blob.h @@ -94,7 +94,40 @@ GIT_EXTERN(const void *) git_blob_rawcontent(const git_blob *blob); * @param blob pointer to the blob * @return size on bytes */ -GIT_EXTERN(git_off_t) git_blob_rawsize(const git_blob *blob); +GIT_EXTERN(git_object_size_t) git_blob_rawsize(const git_blob *blob); + +/** + * Flags to control the functionality of `git_blob_filter`. + */ +typedef enum { + /** When set, filters will not be applied to binary files. */ + GIT_BLOB_FILTER_CHECK_FOR_BINARY = (1 << 0), + + /** + * When set, filters will not load configuration from the + * system-wide `gitattributes` in `/etc` (or system equivalent). + */ + GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES = (1 << 1), + + /** + * When set, filters will be loaded from a `.gitattributes` file + * in the HEAD commit. + */ + GIT_BLOB_FILTER_ATTTRIBUTES_FROM_HEAD = (1 << 2), +} git_blob_filter_flag_t; + +/** + * The options used when applying filter options to a file. + */ +typedef struct { + int version; + + /** Flags to control the filtering process, see `git_blob_filter_flag_t` above */ + uint32_t flags; +} git_blob_filter_options; + +#define GIT_BLOB_FILTER_OPTIONS_VERSION 1 +#define GIT_BLOB_FILTER_OPTIONS_INIT {GIT_BLOB_FILTER_OPTIONS_VERSION, GIT_BLOB_FILTER_CHECK_FOR_BINARY} /** * Get a buffer with the filtered content of a blob. @@ -115,15 +148,14 @@ GIT_EXTERN(git_off_t) git_blob_rawsize(const git_blob *blob); * @param out The git_buf to be filled in * @param blob Pointer to the blob * @param as_path Path used for file attribute lookups, etc. - * @param check_for_binary_data Should this test if blob content contains - * NUL bytes / looks like binary data before applying filters? + * @param opts Options to use for filtering the blob * @return 0 on success or an error code */ -GIT_EXTERN(int) git_blob_filtered_content( +GIT_EXTERN(int) git_blob_filter( git_buf *out, git_blob *blob, const char *as_path, - int check_for_binary_data); + git_blob_filter_options *opts); /** * Read a file from the working folder of a repository @@ -136,7 +168,7 @@ GIT_EXTERN(int) git_blob_filtered_content( * relative to the repository's working dir * @return 0 or an error code */ -GIT_EXTERN(int) git_blob_create_fromworkdir(git_oid *id, git_repository *repo, const char *relative_path); +GIT_EXTERN(int) git_blob_create_from_workdir(git_oid *id, git_repository *repo, const char *relative_path); /** * Read a file from the filesystem and write its content @@ -148,7 +180,7 @@ GIT_EXTERN(int) git_blob_create_fromworkdir(git_oid *id, git_repository *repo, c * @param path file from which the blob will be created * @return 0 or an error code */ -GIT_EXTERN(int) git_blob_create_fromdisk(git_oid *id, git_repository *repo, const char *path); +GIT_EXTERN(int) git_blob_create_from_disk(git_oid *id, git_repository *repo, const char *path); /** * Create a stream to write a new blob into the object db @@ -156,12 +188,12 @@ GIT_EXTERN(int) git_blob_create_fromdisk(git_oid *id, git_repository *repo, cons * This function may need to buffer the data on disk and will in * general not be the right choice if you know the size of the data * to write. If you have data in memory, use - * `git_blob_create_frombuffer()`. If you do not, but know the size of + * `git_blob_create_from_buffer()`. If you do not, but know the size of * the contents (and don't want/need to perform filtering), use * `git_odb_open_wstream()`. * * Don't close this stream yourself but pass it to - * `git_blob_create_fromstream_commit()` to commit the write to the + * `git_blob_create_from_stream_commit()` to commit the write to the * object db and get the object id. * * If the `hintpath` parameter is filled, it will be used to determine @@ -175,7 +207,7 @@ GIT_EXTERN(int) git_blob_create_fromdisk(git_oid *id, git_repository *repo, cons * to apply onto the content of the blob to be created. * @return 0 or error code */ -GIT_EXTERN(int) git_blob_create_fromstream( +GIT_EXTERN(int) git_blob_create_from_stream( git_writestream **out, git_repository *repo, const char *hintpath); @@ -189,7 +221,7 @@ GIT_EXTERN(int) git_blob_create_fromstream( * @param stream the stream to close * @return 0 or an error code */ -GIT_EXTERN(int) git_blob_create_fromstream_commit( +GIT_EXTERN(int) git_blob_create_from_stream_commit( git_oid *out, git_writestream *stream); @@ -202,7 +234,7 @@ GIT_EXTERN(int) git_blob_create_fromstream_commit( * @param len length of the data * @return 0 or an error code */ -GIT_EXTERN(int) git_blob_create_frombuffer( +GIT_EXTERN(int) git_blob_create_from_buffer( git_oid *id, git_repository *repo, const void *buffer, size_t len); /** diff --git a/include/git2/branch.h b/include/git2/branch.h index 8a4ce29a9..ba6235900 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -75,9 +75,9 @@ GIT_EXTERN(int) git_branch_create_from_annotated( /** * Delete an existing branch reference. * - * If the branch is successfully deleted, the passed reference - * object will be invalidated. The reference must be freed manually - * by the user. + * Note that if the deletion succeeds, the reference object will not + * be valid anymore, and should be freed immediately by the user using + * `git_reference_free()`. * * @param branch A valid reference representing a branch * @return 0 on success, or an error code. @@ -126,6 +126,12 @@ GIT_EXTERN(void) git_branch_iterator_free(git_branch_iterator *iter); * The new branch name will be checked for validity. * See `git_tag_create()` for rules about valid names. * + * Note that if the move succeeds, the old reference object will not + + be valid anymore, and should be freed immediately by the user using + + `git_reference_free()`. + * + * @param out New reference object for the updated name. + * * @param branch Current underlying reference of the branch. * * @param new_branch_name Target name of the branch once the move @@ -145,17 +151,14 @@ GIT_EXTERN(int) git_branch_move( * Lookup a branch by its name in a repository. * * The generated reference must be freed by the user. - * * The branch name will be checked for validity. - * See `git_tag_create()` for rules about valid names. + * + * @see git_tag_create for rules about valid names. * * @param out pointer to the looked-up branch reference - * * @param repo the repository to look up the branch - * * @param branch_name Name of the branch to be looked-up; * this name is validated for consistency. - * * @param branch_type Type of the considered branch. This should * be valued with either GIT_BRANCH_LOCAL or GIT_BRANCH_REMOTE. * @@ -169,65 +172,74 @@ GIT_EXTERN(int) git_branch_lookup( git_branch_t branch_type); /** - * Return the name of the given local or remote branch. + * Get the branch name * - * The name of the branch matches the definition of the name - * for git_branch_lookup. That is, if the returned name is given - * to git_branch_lookup() then the reference is returned that - * was given to this function. + * Given a reference object, this will check that it really is a branch (ie. + * it lives under "refs/heads/" or "refs/remotes/"), and return the branch part + * of it. * - * @param out where the pointer of branch name is stored; - * this is valid as long as the ref is not freed. - * @param ref the reference ideally pointing to a branch + * @param out Pointer to the abbreviated reference name. + * Owned by ref, do not free. * - * @return 0 on success; otherwise an error code (e.g., if the - * ref is no local or remote branch). + * @param ref A reference object, ideally pointing to a branch + * + * @return 0 on success; GIT_EINVALID if the reference isn't either a local or + * remote branch, otherwise an error code. */ GIT_EXTERN(int) git_branch_name( const char **out, const git_reference *ref); /** - * Return the reference supporting the remote tracking branch, - * given a local branch reference. + * Get the upstream of a branch * - * @param out Pointer where to store the retrieved - * reference. + * Given a reference, this will return a new reference object corresponding + * to its remote tracking branch. The reference must be a local branch. * + * @see git_branch_upstream_name for details on the resolution. + * + * @param out Pointer where to store the retrieved reference. * @param branch Current underlying reference of the branch. * * @return 0 on success; GIT_ENOTFOUND when no remote tracking - * reference exists, otherwise an error code. + * reference exists, otherwise an error code. */ GIT_EXTERN(int) git_branch_upstream( git_reference **out, const git_reference *branch); /** - * Set the upstream configuration for a given local branch + * Set a branch's upstream branch + * + * This will update the configuration to set the branch named `branch_name` as the upstream of `branch`. + * Pass a NULL name to unset the upstream information. + * + * @note the actual tracking reference must have been already created for the + * operation to succeed. * * @param branch the branch to configure + * @param branch_name remote-tracking or local branch to set as upstream. * - * @param upstream_name remote-tracking or local branch to set as - * upstream. Pass NULL to unset. - * - * @return 0 or an error code + * @return 0 on success; GIT_ENOTFOUND if there's no branch named `branch_name` + * or an error code */ -GIT_EXTERN(int) git_branch_set_upstream(git_reference *branch, const char *upstream_name); +GIT_EXTERN(int) git_branch_set_upstream( + git_reference *branch, + const char *branch_name); /** - * Return the name of the reference supporting the remote tracking branch, - * given the name of a local branch reference. + * Get the upstream name of a branch * - * @param out Pointer to the user-allocated git_buf which will be - * filled with the name of the reference. - * - * @param repo the repository where the branches live + * Given a local branch, this will return its remote-tracking branch information, + * as a full reference name, ie. "feature/nice" would become + * "refs/remote/origin/feature/nice", depending on that branch's configuration. * + * @param out the buffer into which the name will be written. + * @param repo the repository where the branches live. * @param refname reference name of the local branch. * - * @return 0, GIT_ENOTFOUND when no remote tracking reference exists, - * otherwise an error code. + * @return 0 on success, GIT_ENOTFOUND when no remote tracking reference exists, + * or an error code. */ GIT_EXTERN(int) git_branch_upstream_name( git_buf *out, @@ -235,50 +247,55 @@ GIT_EXTERN(int) git_branch_upstream_name( const char *refname); /** - * Determine if the current local branch is pointed at by HEAD. + * Determine if HEAD points to the given branch * - * @param branch Current underlying reference of the branch. + * @param branch A reference to a local branch. * - * @return 1 if HEAD points at the branch, 0 if it isn't, - * error code otherwise. + * @return 1 if HEAD points at the branch, 0 if it isn't, or a negative value + * as an error code. */ GIT_EXTERN(int) git_branch_is_head( const git_reference *branch); /** - * Determine if the current branch is checked out in any linked - * repository. + * Determine if any HEAD points to the current branch * - * @param branch Reference to the branch. + * This will iterate over all known linked repositories (usually in the form of + * worktrees) and report whether any HEAD is pointing at the current branch. * - * @return 1 if branch is checked out, 0 if it isn't, - * error code otherwise. + * @param branch A reference to a local branch. + * + * @return 1 if branch is checked out, 0 if it isn't, an error code otherwise. */ GIT_EXTERN(int) git_branch_is_checked_out( const git_reference *branch); /** - * Return the name of remote that the remote tracking branch belongs to. + * Find the remote name of a remote-tracking branch * - * @param out Pointer to the user-allocated git_buf which will be filled with the name of the remote. + * This will return the name of the remote whose fetch refspec is matching + * the given branch. E.g. given a branch "refs/remotes/test/master", it will + * extract the "test" part. If refspecs from multiple remotes match, + * the function will return GIT_EAMBIGUOUS. * + * @param out The buffer into which the name will be written. * @param repo The repository where the branch lives. + * @param refname complete name of the remote tracking branch. * - * @param canonical_branch_name name of the remote tracking branch. - * - * @return 0, GIT_ENOTFOUND - * when no remote matching remote was found, - * GIT_EAMBIGUOUS when the branch maps to several remotes, - * otherwise an error code. + * @return 0 on success, GIT_ENOTFOUND when no matching remote was found, + * GIT_EAMBIGUOUS when the branch maps to several remotes, + * otherwise an error code. */ GIT_EXTERN(int) git_branch_remote_name( git_buf *out, git_repository *repo, - const char *canonical_branch_name); - + const char *refname); /** - * Retrieve the name of the upstream remote of a local branch + * Retrieve the upstream remote of a local branch + * + * This will return the currently configured "branch.*.remote" for a given + * branch. This branch must be local. * * @param buf the buffer into which to write the name * @param repo the repository in which to look diff --git a/include/git2/buffer.h b/include/git2/buffer.h index 0027678b8..926f1332d 100644 --- a/include/git2/buffer.h +++ b/include/git2/buffer.h @@ -32,26 +32,32 @@ GIT_BEGIN_DECL * a block of memory they hold. In this case, libgit2 will not resize or * free the memory, but will read from it as needed. * - * A `git_buf` is a public structure with three fields: - * - * - `ptr` points to the start of the allocated memory. If it is NULL, - * then the `git_buf` is considered empty and libgit2 will feel free - * to overwrite it with new data. - * - * - `size` holds the size (in bytes) of the data that is actually used. - * - * - `asize` holds the known total amount of allocated memory if the `ptr` - * was allocated by libgit2. It may be larger than `size`. If `ptr` - * was not allocated by libgit2 and should not be resized and/or freed, - * then `asize` will be set to zero. - * * Some APIs may occasionally do something slightly unusual with a buffer, * such as setting `ptr` to a value that was passed in by the user. In * those cases, the behavior will be clearly documented by the API. */ typedef struct { + /** + * The buffer contents. + * + * `ptr` points to the start of the allocated memory. If it is NULL, + * then the `git_buf` is considered empty and libgit2 will feel free + * to overwrite it with new data. + */ char *ptr; - size_t asize, size; + + /** + * `asize` holds the known total amount of allocated memory if the `ptr` + * was allocated by libgit2. It may be larger than `size`. If `ptr` + * was not allocated by libgit2 and should not be resized and/or freed, + * then `asize` will be set to zero. + */ + size_t asize; + + /** + * `size` holds the size (in bytes) of the data that is actually used. + */ + size_t size; } git_buf; /** diff --git a/include/git2/cert.h b/include/git2/cert.h new file mode 100644 index 000000000..e8cd2d180 --- /dev/null +++ b/include/git2/cert.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_cert_h__ +#define INCLUDE_git_cert_h__ + +#include "common.h" + +/** + * @file git2/cert.h + * @brief Git certificate objects + * @defgroup git_cert Certificate objects + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Type of host certificate structure that is passed to the check callback + */ +typedef enum git_cert_t { + /** + * No information about the certificate is available. This may + * happen when using curl. + */ + GIT_CERT_NONE, + /** + * The `data` argument to the callback will be a pointer to + * the DER-encoded data. + */ + GIT_CERT_X509, + /** + * The `data` argument to the callback will be a pointer to a + * `git_cert_hostkey` structure. + */ + GIT_CERT_HOSTKEY_LIBSSH2, + /** + * The `data` argument to the callback will be a pointer to a + * `git_strarray` with `name:content` strings containing + * information about the certificate. This is used when using + * curl. + */ + GIT_CERT_STRARRAY, +} git_cert_t; + +/** + * Parent type for `git_cert_hostkey` and `git_cert_x509`. + */ +struct git_cert { + /** + * Type of certificate. A `GIT_CERT_` value. + */ + git_cert_t cert_type; +}; + +/** + * Callback for the user's custom certificate checks. + * + * @param cert The host certificate + * @param valid Whether the libgit2 checks (OpenSSL or WinHTTP) think + * this certificate is valid + * @param host Hostname of the host libgit2 connected to + * @param payload Payload provided by the caller + * @return 0 to proceed with the connection, < 0 to fail the connection + * or > 0 to indicate that the callback refused to act and that + * the existing validity determination should be honored + */ +typedef int GIT_CALLBACK(git_transport_certificate_check_cb)(git_cert *cert, int valid, const char *host, void *payload); + +/** + * Type of SSH host fingerprint + */ +typedef enum { + /** MD5 is available */ + GIT_CERT_SSH_MD5 = (1 << 0), + /** SHA-1 is available */ + GIT_CERT_SSH_SHA1 = (1 << 1), + /** SHA-256 is available */ + GIT_CERT_SSH_SHA256 = (1 << 2), +} git_cert_ssh_t; + +/** + * Hostkey information taken from libssh2 + */ +typedef struct { + git_cert parent; /**< The parent cert */ + + /** + * A hostkey type from libssh2, either + * `GIT_CERT_SSH_MD5` or `GIT_CERT_SSH_SHA1` + */ + git_cert_ssh_t type; + + /** + * Hostkey hash. If type has `GIT_CERT_SSH_MD5` set, this will + * have the MD5 hash of the hostkey. + */ + unsigned char hash_md5[16]; + + /** + * Hostkey hash. If type has `GIT_CERT_SSH_SHA1` set, this will + * have the SHA-1 hash of the hostkey. + */ + unsigned char hash_sha1[20]; + + /** + * Hostkey hash. If type has `GIT_CERT_SSH_SHA256` set, this will + * have the SHA-256 hash of the hostkey. + */ + unsigned char hash_sha256[32]; +} git_cert_hostkey; + +/** + * X.509 certificate information + */ +typedef struct { + git_cert parent; /**< The parent cert */ + + /** + * Pointer to the X.509 certificate data + */ + void *data; + + /** + * Length of the memory block pointed to by `data`. + */ + size_t len; +} git_cert_x509; + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 3fb3fc127..3c87001bf 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -106,10 +106,22 @@ GIT_BEGIN_DECL typedef enum { GIT_CHECKOUT_NONE = 0, /**< default is a dry run, no actual updates */ - /** Allow safe updates that cannot overwrite uncommitted data */ + /** + * Allow safe updates that cannot overwrite uncommitted data. + * If the uncommitted changes don't conflict with the checked out files, + * the checkout will still proceed, leaving the changes intact. + * + * Mutually exclusive with GIT_CHECKOUT_FORCE. + * GIT_CHECKOUT_FORCE takes precedence over GIT_CHECKOUT_SAFE. + */ GIT_CHECKOUT_SAFE = (1u << 0), - /** Allow all updates to force working directory to look like index */ + /** + * Allow all updates to force working directory to look like index. + * + * Mutually exclusive with GIT_CHECKOUT_SAFE. + * GIT_CHECKOUT_FORCE takes precedence over GIT_CHECKOUT_SAFE. + */ GIT_CHECKOUT_FORCE = (1u << 1), @@ -213,6 +225,7 @@ typedef enum { GIT_CHECKOUT_NOTIFY_ALL = 0x0FFFFu } git_checkout_notify_t; +/** Checkout performance-reporting structure */ typedef struct { size_t mkdir_calls; size_t stat_calls; @@ -244,11 +257,11 @@ typedef void GIT_CALLBACK(git_checkout_perfdata_cb)( * Checkout options structure * * Initialize with `GIT_CHECKOUT_OPTIONS_INIT`. Alternatively, you can - * use `git_checkout_init_options`. + * use `git_checkout_options_init`. * */ typedef struct git_checkout_options { - unsigned int version; + unsigned int version; /**< The version */ unsigned int checkout_strategy; /**< default will be a safe checkout */ @@ -258,29 +271,46 @@ typedef struct git_checkout_options { int file_open_flags; /**< default is O_CREAT | O_TRUNC | O_WRONLY */ unsigned int notify_flags; /**< see `git_checkout_notify_t` above */ + + /** + * Optional callback to get notifications on specific file states. + * @see git_checkout_notify_t + */ git_checkout_notify_cb notify_cb; + + /** Payload passed to notify_cb */ void *notify_payload; /** Optional callback to notify the consumer of checkout progress. */ git_checkout_progress_cb progress_cb; + + /** Payload passed to progress_cb */ void *progress_payload; - /** When not zeroed out, array of fnmatch patterns specifying which - * paths should be taken into account, otherwise all files. Use - * GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH to treat as simple list. + /** + * A list of wildmatch patterns or paths. + * + * By default, all paths are processed. If you pass an array of wildmatch + * patterns, those will be used to filter which paths should be taken into + * account. + * + * Use GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH to treat as a simple list. */ git_strarray paths; - /** The expected content of the working directory; defaults to HEAD. - * If the working directory does not match this baseline information, - * that will produce a checkout conflict. + /** + * The expected content of the working directory; defaults to HEAD. + * + * If the working directory does not match this baseline information, + * that will produce a checkout conflict. */ git_tree *baseline; - /** Like `baseline` above, though expressed as an index. This - * option overrides `baseline`. + /** + * Like `baseline` above, though expressed as an index. This + * option overrides `baseline`. */ - git_index *baseline_index; /**< expected content of workdir, expressed as an index. */ + git_index *baseline_index; const char *target_directory; /**< alternative checkout path to workdir */ @@ -290,6 +320,8 @@ typedef struct git_checkout_options { /** Optional callback to notify the consumer of performance data. */ git_checkout_perfdata_cb perfdata_cb; + + /** Payload passed to perfdata_cb */ void *perfdata_payload; } git_checkout_options; @@ -306,7 +338,7 @@ typedef struct git_checkout_options { * @param version The struct version; pass `GIT_CHECKOUT_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_checkout_init_options( +GIT_EXTERN(int) git_checkout_options_init( git_checkout_options *opts, unsigned int version); diff --git a/include/git2/cherrypick.h b/include/git2/cherrypick.h index ca6f72075..0e6a252e6 100644 --- a/include/git2/cherrypick.h +++ b/include/git2/cherrypick.h @@ -46,7 +46,7 @@ typedef struct { * @param version The struct version; pass `GIT_CHERRYPICK_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_cherrypick_init_options( +GIT_EXTERN(int) git_cherrypick_options_init( git_cherrypick_options *opts, unsigned int version); @@ -59,8 +59,8 @@ GIT_EXTERN(int) git_cherrypick_init_options( * @param out pointer to store the index result in * @param repo the repository that contains the given commits * @param cherrypick_commit the commit to cherry-pick - * @param our_commit the commit to revert against (eg, HEAD) - * @param mainline the parent of the revert commit, if it is a merge + * @param our_commit the commit to cherry-pick against (eg, HEAD) + * @param mainline the parent of the `cherrypick_commit`, if it is a merge * @param merge_options the merge options (or null for defaults) * @return zero on success, -1 on failure. */ diff --git a/include/git2/clone.h b/include/git2/clone.h index 790a82832..2d6f68705 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -97,7 +97,7 @@ typedef int GIT_CALLBACK(git_repository_create_cb)( * Clone options structure * * Initialize with `GIT_CLONE_OPTIONS_INIT`. Alternatively, you can - * use `git_clone_init_options`. + * use `git_clone_options_init`. * */ typedef struct git_clone_options { @@ -178,7 +178,7 @@ typedef struct git_clone_options { * @param version The struct version; pass `GIT_CLONE_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_clone_init_options( +GIT_EXTERN(int) git_clone_options_init( git_clone_options *opts, unsigned int version); diff --git a/include/git2/commit.h b/include/git2/commit.h index 7e0409cc7..e6c4656a9 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -480,7 +480,8 @@ GIT_EXTERN(int) git_commit_create_buffer( * * @param out the resulting commit id * @param commit_content the content of the unsigned commit object - * @param signature the signature to add to the commit + * @param signature the signature to add to the commit. Leave `NULL` + * to create a commit without adding a signature field. * @param signature_field which header field should contain this * signature. Leave `NULL` for the default of "gpgsig" * @return 0 or an error code @@ -501,6 +502,27 @@ GIT_EXTERN(int) git_commit_create_with_signature( */ GIT_EXTERN(int) git_commit_dup(git_commit **out, git_commit *source); +/** + * Commit signing callback. + * + * The callback will be called with the commit content, giving a user an + * opportunity to sign the commit content. The signature_field + * buf may be left empty to specify the default field "gpgsig". + * + * Signatures can take the form of any string, and can be created on an arbitrary + * header field. Signatures are most commonly used for verifying authorship of a + * commit using GPG or a similar cryptographically secure signing algorithm. + * See https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work for more + * details. + * + * When the callback: + * - returns GIT_PASSTHROUGH, no signature will be added to the commit. + * - returns < 0, commit creation will be aborted. + * - returns GIT_OK, the signature parameter is expected to be filled. + */ +typedef int (*git_commit_signing_cb)( + git_buf *signature, git_buf *signature_field, const char *commit_content, void *payload); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/common.h b/include/git2/common.h index 70c02a48c..d6696061d 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -21,10 +21,7 @@ #endif #if defined(_MSC_VER) && _MSC_VER < 1800 - GIT_BEGIN_DECL -# include "inttypes.h" - GIT_END_DECL -/** This check is needed for importing this file in an iOS/OS X framework throws an error in Xcode otherwise.*/ +# include #elif !defined(__CLANG_INTTYPES_H) # include #endif @@ -120,8 +117,9 @@ GIT_BEGIN_DECL * @param major Store the major version number * @param minor Store the minor version number * @param rev Store the revision (patch) number + * @return 0 on success or an error code on failure */ -GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev); +GIT_EXTERN(int) git_libgit2_version(int *major, int *minor, int *rev); /** * Combinations of these values describe the features with which libgit2 @@ -205,7 +203,9 @@ typedef enum { GIT_OPT_SET_ALLOCATOR, GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, GIT_OPT_GET_PACK_MAX_OBJECTS, - GIT_OPT_SET_PACK_MAX_OBJECTS + GIT_OPT_SET_PACK_MAX_OBJECTS, + GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, + GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE } git_libgit2_opt_t; /** @@ -395,6 +395,15 @@ typedef enum { * > Set the maximum number of objects libgit2 will allow in a pack * > file when downloading a pack file from a remote. * + * opts(GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, int enabled) + * > This will cause .keep file existence checks to be skipped when + * > accessing packfiles, which can help performance with remote filesystems. + * + * opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, int enabled) + * > When connecting to a server using NTLM or Negotiate + * > authentication, use expect/continue when POSTing data. + * > This option is not available on Windows. + * * @param option Option key * @param ... value to set the option * @return 0 on success, <0 on failure diff --git a/include/git2/config.h b/include/git2/config.h index 0bea323b2..abf5bbbd0 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -92,20 +92,20 @@ typedef struct git_config_iterator git_config_iterator; * Config var type */ typedef enum { - GIT_CVAR_FALSE = 0, - GIT_CVAR_TRUE = 1, - GIT_CVAR_INT32, - GIT_CVAR_STRING -} git_cvar_t; + GIT_CONFIGMAP_FALSE = 0, + GIT_CONFIGMAP_TRUE = 1, + GIT_CONFIGMAP_INT32, + GIT_CONFIGMAP_STRING +} git_configmap_t; /** * Mapping from config variables to values. */ typedef struct { - git_cvar_t cvar_type; + git_configmap_t type; const char *str_match; int map_value; -} git_cvar_map; +} git_configmap; /** * Locate the path to the global configuration file @@ -623,7 +623,7 @@ GIT_EXTERN(int) git_config_foreach_match( * * A mapping array looks as follows: * - * git_cvar_map autocrlf_mapping[] = { + * git_configmap autocrlf_mapping[] = { * {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, * {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, * {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT}, @@ -644,7 +644,7 @@ GIT_EXTERN(int) git_config_foreach_match( * @param out place to store the result of the mapping * @param cfg config file to get the variables from * @param name name of the config variable to lookup - * @param maps array of `git_cvar_map` objects specifying the possible mappings + * @param maps array of `git_configmap` objects specifying the possible mappings * @param map_n number of mapping objects in `maps` * @return 0 on success, error code otherwise */ @@ -652,20 +652,20 @@ GIT_EXTERN(int) git_config_get_mapped( int *out, const git_config *cfg, const char *name, - const git_cvar_map *maps, + const git_configmap *maps, size_t map_n); /** * Maps a string value to an integer constant * * @param out place to store the result of the parsing - * @param maps array of `git_cvar_map` objects specifying the possible mappings + * @param maps array of `git_configmap` objects specifying the possible mappings * @param map_n number of mapping objects in `maps` * @param value value to parse */ GIT_EXTERN(int) git_config_lookup_map_value( int *out, - const git_cvar_map *maps, + const git_configmap *maps, size_t map_n, const char *value); diff --git a/include/git2/cred_helpers.h b/include/git2/cred_helpers.h index 1416d5642..3721b6d8a 100644 --- a/include/git2/cred_helpers.h +++ b/include/git2/cred_helpers.h @@ -7,47 +7,9 @@ #ifndef INCLUDE_git_cred_helpers_h__ #define INCLUDE_git_cred_helpers_h__ -#include "transport.h" - -/** - * @file git2/cred_helpers.h - * @brief Utility functions for credential management - * @defgroup git_cred_helpers credential management helpers - * @ingroup Git - * @{ - */ -GIT_BEGIN_DECL - -/** - * Payload for git_cred_stock_userpass_plaintext. - */ -typedef struct git_cred_userpass_payload { - const char *username; - const char *password; -} git_cred_userpass_payload; - - -/** - * Stock callback usable as a git_cred_acquire_cb. This calls - * git_cred_userpass_plaintext_new unless the protocol has not specified - * `GIT_CREDTYPE_USERPASS_PLAINTEXT` as an allowed type. - * - * @param cred The newly created credential object. - * @param url The resource for which we are demanding a credential. - * @param user_from_url The username that was embedded in a "user\@host" - * remote url, or NULL if not included. - * @param allowed_types A bitmask stating which cred types are OK to return. - * @param payload The payload provided when specifying this callback. (This is - * interpreted as a `git_cred_userpass_payload*`.) - */ -GIT_EXTERN(int) git_cred_userpass( - git_cred **cred, - const char *url, - const char *user_from_url, - unsigned int allowed_types, - void *payload); - - -/** @} */ -GIT_END_DECL +/* These declarations have moved. */ +#ifndef GIT_DEPRECATE_HARD +# include "git2/credential_helpers.h" +#endif + #endif diff --git a/include/git2/credential.h b/include/git2/credential.h new file mode 100644 index 000000000..9426a6ea2 --- /dev/null +++ b/include/git2/credential.h @@ -0,0 +1,314 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_credential_h__ +#define INCLUDE_git_credential_h__ + +#include "common.h" + +/** + * @file git2/credential.h + * @brief Git authentication & credential management + * @defgroup git_credential Authentication & credential management + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Supported credential types + * + * This represents the various types of authentication methods supported by + * the library. + */ +typedef enum { + /** + * A vanilla user/password request + * @see git_credential_userpass_plaintext_new + */ + GIT_CREDENTIAL_USERPASS_PLAINTEXT = (1u << 0), + + /** + * An SSH key-based authentication request + * @see git_credential_ssh_key_new + */ + GIT_CREDENTIAL_SSH_KEY = (1u << 1), + + /** + * An SSH key-based authentication request, with a custom signature + * @see git_credential_ssh_custom_new + */ + GIT_CREDENTIAL_SSH_CUSTOM = (1u << 2), + + /** + * An NTLM/Negotiate-based authentication request. + * @see git_credential_default + */ + GIT_CREDENTIAL_DEFAULT = (1u << 3), + + /** + * An SSH interactive authentication request + * @see git_credential_ssh_interactive_new + */ + GIT_CREDENTIAL_SSH_INTERACTIVE = (1u << 4), + + /** + * Username-only authentication request + * + * Used as a pre-authentication step if the underlying transport + * (eg. SSH, with no username in its URL) does not know which username + * to use. + * + * @see git_credential_username_new + */ + GIT_CREDENTIAL_USERNAME = (1u << 5), + + /** + * An SSH key-based authentication request + * + * Allows credentials to be read from memory instead of files. + * Note that because of differences in crypto backend support, it might + * not be functional. + * + * @see git_credential_ssh_key_memory_new + */ + GIT_CREDENTIAL_SSH_MEMORY = (1u << 6), +} git_credential_t; + +/** + * The base structure for all credential types + */ +typedef struct git_credential git_credential; + +typedef struct git_credential_userpass_plaintext git_credential_userpass_plaintext; + +/** Username-only credential information */ +typedef struct git_credential_username git_credential_username; + +/** A key for NTLM/Kerberos "default" credentials */ +typedef struct git_credential git_credential_default; + +/** + * A ssh key from disk + */ +typedef struct git_credential_ssh_key git_credential_ssh_key; + +/** + * Keyboard-interactive based ssh authentication + */ +typedef struct git_credential_ssh_interactive git_credential_ssh_interactive; + +/** + * A key with a custom signature function + */ +typedef struct git_credential_ssh_custom git_credential_ssh_custom; + +/** + * Credential acquisition callback. + * + * This callback is usually involved any time another system might need + * authentication. As such, you are expected to provide a valid + * git_credential object back, depending on allowed_types (a + * git_credential_t bitmask). + * + * Note that most authentication details are your responsibility - this + * callback will be called until the authentication succeeds, or you report + * an error. As such, it's easy to get in a loop if you fail to stop providing + * the same incorrect credentials. + * + * @param out The newly created credential object. + * @param url The resource for which we are demanding a credential. + * @param username_from_url The username that was embedded in a "user\@host" + * remote url, or NULL if not included. + * @param allowed_types A bitmask stating which credential types are OK to return. + * @param payload The payload provided when specifying this callback. + * @return 0 for success, < 0 to indicate an error, > 0 to indicate + * no credential was acquired + */ +typedef int GIT_CALLBACK(git_credential_acquire_cb)( + git_credential **out, + const char *url, + const char *username_from_url, + unsigned int allowed_types, + void *payload); + +/** + * Free a credential. + * + * This is only necessary if you own the object; that is, if you are a + * transport. + * + * @param cred the object to free + */ +GIT_EXTERN(void) git_credential_free(git_credential *cred); + +/** + * Check whether a credential object contains username information. + * + * @param cred object to check + * @return 1 if the credential object has non-NULL username, 0 otherwise + */ +GIT_EXTERN(int) git_credential_has_username(git_credential *cred); + +/** + * Return the username associated with a credential object. + * + * @param cred object to check + * @return the credential username, or NULL if not applicable + */ +GIT_EXTERN(const char *) git_credential_get_username(git_credential *cred); + +/** + * Create a new plain-text username and password credential object. + * The supplied credential parameter will be internally duplicated. + * + * @param out The newly created credential object. + * @param username The username of the credential. + * @param password The password of the credential. + * @return 0 for success or an error code for failure + */ +GIT_EXTERN(int) git_credential_userpass_plaintext_new( + git_credential **out, + const char *username, + const char *password); + +/** + * Create a "default" credential usable for Negotiate mechanisms like NTLM + * or Kerberos authentication. + * + * @param out The newly created credential object. + * @return 0 for success or an error code for failure + */ +GIT_EXTERN(int) git_credential_default_new(git_credential **out); + +/** + * Create a credential to specify a username. + * + * This is used with ssh authentication to query for the username if + * none is specified in the url. + * + * @param out The newly created credential object. + * @param username The username to authenticate with + * @return 0 for success or an error code for failure + */ +GIT_EXTERN(int) git_credential_username_new(git_credential **out, const char *username); + +/** + * Create a new passphrase-protected ssh key credential object. + * The supplied credential parameter will be internally duplicated. + * + * @param out The newly created credential object. + * @param username username to use to authenticate + * @param publickey The path to the public key of the credential. + * @param privatekey The path to the private key of the credential. + * @param passphrase The passphrase of the credential. + * @return 0 for success or an error code for failure + */ +GIT_EXTERN(int) git_credential_ssh_key_new( + git_credential **out, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase); + +/** + * Create a new ssh key credential object reading the keys from memory. + * + * @param out The newly created credential object. + * @param username username to use to authenticate. + * @param publickey The public key of the credential. + * @param privatekey The private key of the credential. + * @param passphrase The passphrase of the credential. + * @return 0 for success or an error code for failure + */ +GIT_EXTERN(int) git_credential_ssh_key_memory_new( + git_credential **out, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase); + +/* + * If the user hasn't included libssh2.h before git2.h, we need to + * define a few types for the callback signatures. + */ +#ifndef LIBSSH2_VERSION +typedef struct _LIBSSH2_SESSION LIBSSH2_SESSION; +typedef struct _LIBSSH2_USERAUTH_KBDINT_PROMPT LIBSSH2_USERAUTH_KBDINT_PROMPT; +typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE LIBSSH2_USERAUTH_KBDINT_RESPONSE; +#endif + +typedef void GIT_CALLBACK(git_credential_ssh_interactive_cb)( + const char *name, + int name_len, + const char *instruction, int instruction_len, + int num_prompts, const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, + void **abstract); + + +/** + * Create a new ssh keyboard-interactive based credential object. + * The supplied credential parameter will be internally duplicated. + * + * @param username Username to use to authenticate. + * @param prompt_callback The callback method used for prompts. + * @param payload Additional data to pass to the callback. + * @return 0 for success or an error code for failure. + */ +GIT_EXTERN(int) git_credential_ssh_interactive_new( + git_credential **out, + const char *username, + git_credential_ssh_interactive_cb prompt_callback, + void *payload); + +/** + * Create a new ssh key credential object used for querying an ssh-agent. + * The supplied credential parameter will be internally duplicated. + * + * @param out The newly created credential object. + * @param username username to use to authenticate + * @return 0 for success or an error code for failure + */ +GIT_EXTERN(int) git_credential_ssh_key_from_agent( + git_credential **out, + const char *username); + +typedef int GIT_CALLBACK(git_credential_sign_cb)( + LIBSSH2_SESSION *session, + unsigned char **sig, size_t *sig_len, + const unsigned char *data, size_t data_len, + void **abstract); + +/** + * Create an ssh key credential with a custom signing function. + * + * This lets you use your own function to sign the challenge. + * + * This function and its credential type is provided for completeness + * and wraps `libssh2_userauth_publickey()`, which is undocumented. + * + * The supplied credential parameter will be internally duplicated. + * + * @param out The newly created credential object. + * @param username username to use to authenticate + * @param publickey The bytes of the public key. + * @param publickey_len The length of the public key in bytes. + * @param sign_callback The callback method to sign the data during the challenge. + * @param payload Additional data to pass to the callback. + * @return 0 for success or an error code for failure + */ +GIT_EXTERN(int) git_credential_ssh_custom_new( + git_credential **out, + const char *username, + const char *publickey, + size_t publickey_len, + git_credential_sign_cb sign_callback, + void *payload); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/credential_helpers.h b/include/git2/credential_helpers.h new file mode 100644 index 000000000..9a70ecb38 --- /dev/null +++ b/include/git2/credential_helpers.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_credential_helpers_h__ +#define INCLUDE_git_credential_helpers_h__ + +#include "transport.h" + +/** + * @file git2/credential_helpers.h + * @brief Utility functions for credential management + * @defgroup git_credential_helpers credential management helpers + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Payload for git_credential_userpass_plaintext. + */ +typedef struct git_credential_userpass_payload { + const char *username; + const char *password; +} git_credential_userpass_payload; + + +/** + * Stock callback usable as a git_credential_acquire_cb. This calls + * git_cred_userpass_plaintext_new unless the protocol has not specified + * `GIT_CREDENTIAL_USERPASS_PLAINTEXT` as an allowed type. + * + * @param out The newly created credential object. + * @param url The resource for which we are demanding a credential. + * @param user_from_url The username that was embedded in a "user\@host" + * remote url, or NULL if not included. + * @param allowed_types A bitmask stating which credential types are OK to return. + * @param payload The payload provided when specifying this callback. (This is + * interpreted as a `git_credential_userpass_payload*`.) + */ +GIT_EXTERN(int) git_credential_userpass( + git_credential **out, + const char *url, + const char *user_from_url, + unsigned int allowed_types, + void *payload); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/deprecated.h b/include/git2/deprecated.h index 9d3a091a3..e5e56edae 100644 --- a/include/git2/deprecated.h +++ b/include/git2/deprecated.h @@ -7,18 +7,47 @@ #ifndef INCLUDE_git_deprecated_h__ #define INCLUDE_git_deprecated_h__ +#include "attr.h" +#include "config.h" #include "common.h" +#include "blame.h" #include "buffer.h" +#include "checkout.h" +#include "cherrypick.h" +#include "clone.h" +#include "describe.h" +#include "diff.h" #include "errors.h" #include "index.h" +#include "indexer.h" +#include "merge.h" #include "object.h" +#include "proxy.h" #include "refs.h" +#include "rebase.h" +#include "remote.h" +#include "trace.h" +#include "repository.h" +#include "revert.h" +#include "stash.h" +#include "status.h" +#include "submodule.h" +#include "worktree.h" +#include "credential.h" +#include "credential_helpers.h" /* * Users can avoid deprecated functions by defining `GIT_DEPRECATE_HARD`. */ #ifndef GIT_DEPRECATE_HARD +/* + * The credential structures are now opaque by default, and their + * definition has moved into the `sys/credential.h` header; include + * them here for backward compatibility. + */ +#include "sys/credential.h" + /** * @file git2/deprecated.h * @brief libgit2 deprecated functions and values @@ -27,6 +56,61 @@ */ GIT_BEGIN_DECL +/** @name Deprecated Attribute Constants + * + * These enumeration values are retained for backward compatibility. + * The newer versions of these functions should be preferred in all + * new code. + * + * There is no plan to remove these backward compatibility values at + * this time. + */ +/**@{*/ + +#define GIT_ATTR_UNSPECIFIED_T GIT_ATTR_VALUE_UNSPECIFIED +#define GIT_ATTR_TRUE_T GIT_ATTR_VALUE_TRUE +#define GIT_ATTR_FALSE_T GIT_ATTR_VALUE_FALSE +#define GIT_ATTR_VALUE_T GIT_ATTR_VALUE_STRING + +#define GIT_ATTR_TRUE(attr) GIT_ATTR_IS_TRUE(attr) +#define GIT_ATTR_FALSE(attr) GIT_ATTR_IS_FALSE(attr) +#define GIT_ATTR_UNSPECIFIED(attr) GIT_ATTR_IS_UNSPECIFIED(attr) + +typedef git_attr_value_t git_attr_t; + +/**@}*/ + +/** @name Deprecated Blob Functions + * + * These functions are retained for backward compatibility. The newer + * versions of these functions should be preferred in all new code. + * + * There is no plan to remove these backward compatibility values at + * this time. + */ +/**@{*/ + +GIT_EXTERN(int) git_blob_create_fromworkdir(git_oid *id, git_repository *repo, const char *relative_path); +GIT_EXTERN(int) git_blob_create_fromdisk(git_oid *id, git_repository *repo, const char *path); +GIT_EXTERN(int) git_blob_create_fromstream( + git_writestream **out, + git_repository *repo, + const char *hintpath); +GIT_EXTERN(int) git_blob_create_fromstream_commit( + git_oid *out, + git_writestream *stream); +GIT_EXTERN(int) git_blob_create_frombuffer( + git_oid *id, git_repository *repo, const void *buffer, size_t len); + +/** Deprecated in favor of @see git_blob_filter */ +GIT_EXTERN(int) git_blob_filtered_content( + git_buf *out, + git_blob *blob, + const char *as_path, + int check_for_binary_data); + +/**@}*/ + /** @name Deprecated Buffer Functions * * These functions and enumeration values are retained for backward @@ -52,6 +136,19 @@ GIT_EXTERN(void) git_buf_free(git_buf *buffer); /**@}*/ +/** @name Deprecated Config Functions and Constants + */ +/**@{*/ + +#define GIT_CVAR_FALSE GIT_CONFIGMAP_FALSE +#define GIT_CVAR_TRUE GIT_CONFIGMAP_TRUE +#define GIT_CVAR_INT32 GIT_CONFIGMAP_INT32 +#define GIT_CVAR_STRING GIT_CONFIGMAP_STRING + +typedef git_configmap git_cvar_map; + +/**@}*/ + /** @name Deprecated Error Functions and Constants * * These functions and enumeration values are retained for backward @@ -149,10 +246,11 @@ GIT_EXTERN(void) giterr_set_oom(void); /**@}*/ -/** @name Deprecated Index Constants +/** @name Deprecated Index Functions and Constants * - * These enumeration values are retained for backward compatibility. - * The newer versions of these values should be preferred in all new code. + * These functions and enumeration values are retained for backward + * compatibility. The newer versions of these values should be + * preferred in all new code. * * There is no plan to remove these backward compatibility values at * this time. @@ -192,6 +290,11 @@ GIT_EXTERN(void) giterr_set_oom(void); #define GIT_INDEXCAP_NO_SYMLINKS GIT_INDEX_CAPABILITY_NO_SYMLINKS #define GIT_INDEXCAP_FROM_OWNER GIT_INDEX_CAPABILITY_FROM_OWNER +GIT_EXTERN(int) git_index_add_frombuffer( + git_index *index, + const git_index_entry *entry, + const void *buffer, size_t len); + /**@}*/ /** @name Deprecated Object Constants @@ -217,6 +320,20 @@ GIT_EXTERN(void) giterr_set_oom(void); #define GIT_OBJ_OFS_DELTA GIT_OBJECT_OFS_DELTA #define GIT_OBJ_REF_DELTA GIT_OBJECT_REF_DELTA +/** + * Get the size in bytes for the structure which + * acts as an in-memory representation of any given + * object type. + * + * For all the core types, this would the equivalent + * of calling `sizeof(git_commit)` if the core types + * were not opaque on the external API. + * + * @param type object type to get its size + * @return size in bytes of the object + */ +GIT_EXTERN(size_t) git_object__size(git_object_t type); + /**@}*/ /** @name Deprecated Reference Constants @@ -243,6 +360,207 @@ GIT_EXTERN(void) giterr_set_oom(void); #define GIT_REF_FORMAT_REFSPEC_PATTERN GIT_REFERENCE_FORMAT_REFSPEC_PATTERN #define GIT_REF_FORMAT_REFSPEC_SHORTHAND GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND +GIT_EXTERN(int) git_tag_create_frombuffer( + git_oid *oid, + git_repository *repo, + const char *buffer, + int force); + +/**@}*/ + +/** @name Deprecated Credential Types + * + * These types are retained for backward compatibility. The newer + * versions of these values should be preferred in all new code. + * + * There is no plan to remove these backward compatibility values at + * this time. + */ + +typedef git_credential git_cred; +typedef git_credential_userpass_plaintext git_cred_userpass_plaintext; +typedef git_credential_username git_cred_username; +typedef git_credential_default git_cred_default; +typedef git_credential_ssh_key git_cred_ssh_key; +typedef git_credential_ssh_interactive git_cred_ssh_interactive; +typedef git_credential_ssh_custom git_cred_ssh_custom; + +typedef git_credential_acquire_cb git_cred_acquire_cb; +typedef git_credential_sign_cb git_cred_sign_callback; +typedef git_credential_sign_cb git_cred_sign_cb; +typedef git_credential_ssh_interactive_cb git_cred_ssh_interactive_callback; +typedef git_credential_ssh_interactive_cb git_cred_ssh_interactive_cb; + +#define git_credtype_t git_credential_t + +#define GIT_CREDTYPE_USERPASS_PLAINTEXT GIT_CREDENTIAL_USERPASS_PLAINTEXT +#define GIT_CREDTYPE_SSH_KEY GIT_CREDENTIAL_SSH_KEY +#define GIT_CREDTYPE_SSH_CUSTOM GIT_CREDENTIAL_SSH_CUSTOM +#define GIT_CREDTYPE_DEFAULT GIT_CREDENTIAL_DEFAULT +#define GIT_CREDTYPE_SSH_INTERACTIVE GIT_CREDENTIAL_SSH_INTERACTIVE +#define GIT_CREDTYPE_USERNAME GIT_CREDENTIAL_USERNAME +#define GIT_CREDTYPE_SSH_MEMORY GIT_CREDENTIAL_SSH_MEMORY + +GIT_EXTERN(void) git_cred_free(git_credential *cred); +GIT_EXTERN(int) git_cred_has_username(git_credential *cred); +GIT_EXTERN(const char *) git_cred_get_username(git_credential *cred); +GIT_EXTERN(int) git_cred_userpass_plaintext_new( + git_credential **out, + const char *username, + const char *password); +GIT_EXTERN(int) git_cred_default_new(git_credential **out); +GIT_EXTERN(int) git_cred_username_new(git_credential **out, const char *username); +GIT_EXTERN(int) git_cred_ssh_key_new( + git_credential **out, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase); +GIT_EXTERN(int) git_cred_ssh_key_memory_new( + git_credential **out, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase); +GIT_EXTERN(int) git_cred_ssh_interactive_new( + git_credential **out, + const char *username, + git_credential_ssh_interactive_cb prompt_callback, + void *payload); +GIT_EXTERN(int) git_cred_ssh_key_from_agent( + git_credential **out, + const char *username); +GIT_EXTERN(int) git_cred_ssh_custom_new( + git_credential **out, + const char *username, + const char *publickey, + size_t publickey_len, + git_credential_sign_cb sign_callback, + void *payload); + +/* Deprecated Credential Helper Types */ + +typedef git_credential_userpass_payload git_cred_userpass_payload; + +GIT_EXTERN(int) git_cred_userpass( + git_credential **out, + const char *url, + const char *user_from_url, + unsigned int allowed_types, + void *payload); + +/**@}*/ + +/** @name Deprecated Trace Callback Types + * + * These types are retained for backward compatibility. The newer + * versions of these values should be preferred in all new code. + * + * There is no plan to remove these backward compatibility values at + * this time. + */ +/**@{*/ + +typedef git_trace_cb git_trace_callback; + +/**@}*/ + +/** @name Deprecated Object ID Types + * + * These types are retained for backward compatibility. The newer + * versions of these values should be preferred in all new code. + * + * There is no plan to remove these backward compatibility values at + * this time. + */ +/**@{*/ + +GIT_EXTERN(int) git_oid_iszero(const git_oid *id); + +/**@}*/ + +/** @name Deprecated Transfer Progress Types + * + * These types are retained for backward compatibility. The newer + * versions of these values should be preferred in all new code. + * + * There is no plan to remove these backward compatibility values at + * this time. + */ +/**@{*/ + +/** + * This structure is used to provide callers information about the + * progress of indexing a packfile. + * + * This type is deprecated, but there is no plan to remove this + * type definition at this time. + */ +typedef git_indexer_progress git_transfer_progress; + +/** + * Type definition for progress callbacks during indexing. + * + * This type is deprecated, but there is no plan to remove this + * type definition at this time. + */ +typedef git_indexer_progress_cb git_transfer_progress_cb; + +/** + * Type definition for push transfer progress callbacks. + * + * This type is deprecated, but there is no plan to remove this + * type definition at this time. + */ +typedef git_push_transfer_progress_cb git_push_transfer_progress; + + /** The type of a remote completion event */ +#define git_remote_completion_type git_remote_completion_t + +/** + * Callback for listing the remote heads + */ +typedef int GIT_CALLBACK(git_headlist_cb)(git_remote_head *rhead, void *payload); + +/**@}*/ + +/** @name Deprecated Options Initialization Functions + * + * These functions are retained for backward compatibility. The newer + * versions of these functions should be preferred in all new code. + * + * There is no plan to remove these backward compatibility functions at + * this time. + */ +/**@{*/ + +GIT_EXTERN(int) git_blame_init_options(git_blame_options *opts, unsigned int version); +GIT_EXTERN(int) git_checkout_init_options(git_checkout_options *opts, unsigned int version); +GIT_EXTERN(int) git_cherrypick_init_options(git_cherrypick_options *opts, unsigned int version); +GIT_EXTERN(int) git_clone_init_options(git_clone_options *opts, unsigned int version); +GIT_EXTERN(int) git_describe_init_options(git_describe_options *opts, unsigned int version); +GIT_EXTERN(int) git_describe_init_format_options(git_describe_format_options *opts, unsigned int version); +GIT_EXTERN(int) git_diff_init_options(git_diff_options *opts, unsigned int version); +GIT_EXTERN(int) git_diff_find_init_options(git_diff_find_options *opts, unsigned int version); +GIT_EXTERN(int) git_diff_format_email_init_options(git_diff_format_email_options *opts, unsigned int version); +GIT_EXTERN(int) git_diff_patchid_init_options(git_diff_patchid_options *opts, unsigned int version); +GIT_EXTERN(int) git_fetch_init_options(git_fetch_options *opts, unsigned int version); +GIT_EXTERN(int) git_indexer_init_options(git_indexer_options *opts, unsigned int version); +GIT_EXTERN(int) git_merge_init_options(git_merge_options *opts, unsigned int version); +GIT_EXTERN(int) git_merge_file_init_input(git_merge_file_input *input, unsigned int version); +GIT_EXTERN(int) git_merge_file_init_options(git_merge_file_options *opts, unsigned int version); +GIT_EXTERN(int) git_proxy_init_options(git_proxy_options *opts, unsigned int version); +GIT_EXTERN(int) git_push_init_options(git_push_options *opts, unsigned int version); +GIT_EXTERN(int) git_rebase_init_options(git_rebase_options *opts, unsigned int version); +GIT_EXTERN(int) git_remote_create_init_options(git_remote_create_options *opts, unsigned int version); +GIT_EXTERN(int) git_repository_init_init_options(git_repository_init_options *opts, unsigned int version); +GIT_EXTERN(int) git_revert_init_options(git_revert_options *opts, unsigned int version); +GIT_EXTERN(int) git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version); +GIT_EXTERN(int) git_status_init_options(git_status_options *opts, unsigned int version); +GIT_EXTERN(int) git_submodule_update_init_options(git_submodule_update_options *opts, unsigned int version); +GIT_EXTERN(int) git_worktree_add_init_options(git_worktree_add_options *opts, unsigned int version); +GIT_EXTERN(int) git_worktree_prune_init_options(git_worktree_prune_options *opts, unsigned int version); + /**@}*/ /** @} */ diff --git a/include/git2/describe.h b/include/git2/describe.h index 56f119b2d..1d2ca1496 100644 --- a/include/git2/describe.h +++ b/include/git2/describe.h @@ -37,7 +37,7 @@ typedef enum { * Describe options structure * * Initialize with `GIT_DESCRIBE_OPTIONS_INIT`. Alternatively, you can - * use `git_describe_init_options`. + * use `git_describe_options_init`. * */ typedef struct git_describe_options { @@ -79,13 +79,13 @@ typedef struct git_describe_options { * @param version The struct version; pass `GIT_DESCRIBE_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_describe_init_options(git_describe_options *opts, unsigned int version); +GIT_EXTERN(int) git_describe_options_init(git_describe_options *opts, unsigned int version); /** * Describe format options structure * * Initialize with `GIT_DESCRIBE_FORMAT_OPTIONS_INIT`. Alternatively, you can - * use `git_describe_format_init_options`. + * use `git_describe_format_options_init`. * */ typedef struct { @@ -126,7 +126,7 @@ typedef struct { * @param version The struct version; pass `GIT_DESCRIBE_FORMAT_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_describe_init_format_options(git_describe_format_options *opts, unsigned int version); +GIT_EXTERN(int) git_describe_format_options_init(git_describe_format_options *opts, unsigned int version); /** * A struct that stores the result of a describe operation. diff --git a/include/git2/diff.h b/include/git2/diff.h index b920385ee..3976ab1b9 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -119,7 +119,7 @@ typedef enum { /** Include unreadable files in the diff */ GIT_DIFF_INCLUDE_UNREADABLE = (1u << 16), - + /** Include unreadable files in the diff */ GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED = (1u << 17), @@ -258,12 +258,12 @@ typedef enum { * abbreviated to something reasonable, like 7 characters. */ typedef struct { - git_oid id; - const char *path; - git_off_t size; - uint32_t flags; - uint16_t mode; - uint16_t id_abbrev; + git_oid id; + const char *path; + git_object_size_t size; + uint32_t flags; + uint16_t mode; + uint16_t id_abbrev; } git_diff_file; /** @@ -451,7 +451,7 @@ typedef struct { * @param version The struct version; pass `GIT_DIFF_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_diff_init_options( +GIT_EXTERN(int) git_diff_options_init( git_diff_options *opts, unsigned int version); @@ -784,7 +784,7 @@ typedef struct { * @param version The struct version; pass `GIT_DIFF_FIND_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_diff_find_init_options( +GIT_EXTERN(int) git_diff_find_options_init( git_diff_find_options *opts, unsigned int version); @@ -1093,6 +1093,7 @@ typedef enum { GIT_DIFF_FORMAT_RAW = 3u, /**< like git diff --raw */ GIT_DIFF_FORMAT_NAME_ONLY = 4u, /**< like git diff --name-only */ GIT_DIFF_FORMAT_NAME_STATUS = 5u, /**< like git diff --name-status */ + GIT_DIFF_FORMAT_PATCH_ID = 6u, /**< git diff as used by git patch-id */ } git_diff_format_t; /** @@ -1378,7 +1379,8 @@ typedef enum { typedef struct { unsigned int version; - git_diff_format_email_flags_t flags; + /** see `git_diff_format_email_flags_t` above */ + uint32_t flags; /** This patch number */ size_t patch_no; @@ -1435,7 +1437,7 @@ GIT_EXTERN(int) git_diff_commit_as_email( git_commit *commit, size_t patch_no, size_t total_patches, - git_diff_format_email_flags_t flags, + uint32_t flags, const git_diff_options *diff_opts); /** @@ -1448,7 +1450,7 @@ GIT_EXTERN(int) git_diff_commit_as_email( * @param version The struct version; pass `GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_diff_format_email_init_options( +GIT_EXTERN(int) git_diff_format_email_options_init( git_diff_format_email_options *opts, unsigned int version); @@ -1456,7 +1458,7 @@ GIT_EXTERN(int) git_diff_format_email_init_options( * Patch ID options structure * * Initialize with `GIT_PATCHID_OPTIONS_INIT`. Alternatively, you can - * use `git_patchid_init_options`. + * use `git_diff_patchid_options_init`. * */ typedef struct git_diff_patchid_options { @@ -1476,7 +1478,7 @@ typedef struct git_diff_patchid_options { * @param version The struct version; pass `GIT_DIFF_PATCHID_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_diff_patchid_init_options( +GIT_EXTERN(int) git_diff_patchid_options_init( git_diff_patchid_options *opts, unsigned int version); diff --git a/include/git2/errors.h b/include/git2/errors.h index 4e19f8925..5c85c4d6c 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -106,7 +106,8 @@ typedef enum { GIT_ERROR_FILESYSTEM, GIT_ERROR_PATCH, GIT_ERROR_WORKTREE, - GIT_ERROR_SHA1 + GIT_ERROR_SHA1, + GIT_ERROR_HTTP } git_error_t; /** @@ -142,8 +143,9 @@ GIT_EXTERN(void) git_error_clear(void); * @param error_class One of the `git_error_t` enum above describing the * general subsystem that is responsible for the error. * @param string The formatted error message to keep + * @return 0 on success or -1 on failure */ -GIT_EXTERN(void) git_error_set_str(int error_class, const char *string); +GIT_EXTERN(int) git_error_set_str(int error_class, const char *string); /** * Set the error message to a special value for memory allocation failure. diff --git a/include/git2/filter.h b/include/git2/filter.h index 436a0f3c8..886059051 100644 --- a/include/git2/filter.h +++ b/include/git2/filter.h @@ -40,7 +40,15 @@ typedef enum { */ typedef enum { GIT_FILTER_DEFAULT = 0u, + + /** Don't error for `safecrlf` violations, allow them to continue. */ GIT_FILTER_ALLOW_UNSAFE = (1u << 0), + + /** Don't load `/etc/gitattributes` (or the system equivalent) */ + GIT_FILTER_NO_SYSTEM_ATTRIBUTES = (1u << 1), + + /** Load attributes from `.gitattributes` in the root of HEAD */ + GIT_FILTER_ATTRIBUTES_FROM_HEAD = (1u << 2), } git_filter_flag_t; /** diff --git a/include/git2/index.h b/include/git2/index.h index 419184cc3..8723aa636 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -143,6 +143,7 @@ typedef enum { GIT_INDEX_ADD_CHECK_PATHSPEC = (1u << 2), } git_index_add_option_t; +/** Git index stage states */ typedef enum { /** * Match any index stage. @@ -571,7 +572,7 @@ GIT_EXTERN(int) git_index_add_bypath(git_index *index, const char *path); * @param len length of the data * @return 0 or an error code */ -GIT_EXTERN(int) git_index_add_frombuffer( +GIT_EXTERN(int) git_index_add_from_buffer( git_index *index, const git_index_entry *entry, const void *buffer, size_t len); diff --git a/include/git2/indexer.h b/include/git2/indexer.h index 94d8785c0..8059e4db3 100644 --- a/include/git2/indexer.h +++ b/include/git2/indexer.h @@ -13,13 +13,57 @@ GIT_BEGIN_DECL +/** A git indexer object */ typedef struct git_indexer git_indexer; +/** + * This structure is used to provide callers information about the + * progress of indexing a packfile, either directly or part of a + * fetch or clone that downloads a packfile. + */ +typedef struct git_indexer_progress { + /** number of objects in the packfile being indexed */ + unsigned int total_objects; + + /** received objects that have been hashed */ + unsigned int indexed_objects; + + /** received_objects: objects which have been downloaded */ + unsigned int received_objects; + + /** + * locally-available objects that have been injected in order + * to fix a thin pack + */ + unsigned int local_objects; + + /** number of deltas in the packfile being indexed */ + unsigned int total_deltas; + + /** received deltas that have been indexed */ + unsigned int indexed_deltas; + + /** size of the packfile received up to now */ + size_t received_bytes; +} git_indexer_progress; + +/** + * Type for progress callbacks during indexing. Return a value less + * than zero to cancel the indexing or download. + * + * @param stats Structure containing information about the state of the tran sfer + * @param payload Payload provided by caller + */ +typedef int GIT_CALLBACK(git_indexer_progress_cb)(const git_indexer_progress *stats, void *payload); + +/** + * Options for indexer configuration + */ typedef struct git_indexer_options { unsigned int version; /** progress_cb function to call with progress information */ - git_transfer_progress_cb progress_cb; + git_indexer_progress_cb progress_cb; /** progress_cb_payload payload for the progress callback */ void *progress_cb_payload; @@ -38,7 +82,7 @@ typedef struct git_indexer_options { * @param version Version of struct; pass `GIT_INDEXER_OPTIONS_VERSION` * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_indexer_init_options( +GIT_EXTERN(int) git_indexer_options_init( git_indexer_options *opts, unsigned int version); @@ -69,7 +113,7 @@ GIT_EXTERN(int) git_indexer_new( * @param size the size of the data in bytes * @param stats stat storage */ -GIT_EXTERN(int) git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats); +GIT_EXTERN(int) git_indexer_append(git_indexer *idx, const void *data, size_t size, git_indexer_progress *stats); /** * Finalize the pack and index @@ -78,7 +122,7 @@ GIT_EXTERN(int) git_indexer_append(git_indexer *idx, const void *data, size_t si * * @param idx the indexer */ -GIT_EXTERN(int) git_indexer_commit(git_indexer *idx, git_transfer_progress *stats); +GIT_EXTERN(int) git_indexer_commit(git_indexer *idx, git_indexer_progress *stats); /** * Get the packfile's hash diff --git a/include/git2/inttypes.h b/include/git2/inttypes.h deleted file mode 100644 index 17364c7f8..000000000 --- a/include/git2/inttypes.h +++ /dev/null @@ -1,309 +0,0 @@ -// ISO C9x compliant inttypes.h for Microsoft Visual Studio -// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 -// -// Copyright (c) 2006 Alexander Chemeris -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// 3. The name of the author may be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef _MSC_VER // [ -#error "Use this header only with Microsoft Visual C++ compilers!" -#endif // _MSC_VER ] - -#ifndef _MSC_INTTYPES_H_ // [ -#define _MSC_INTTYPES_H_ - -#if _MSC_VER > 1000 -#pragma once -#endif - -#if _MSC_VER >= 1600 -#include -#else -#include "stdint.h" -#endif - -// 7.8 Format conversion of integer types - -typedef struct { - intmax_t quot; - intmax_t rem; -} imaxdiv_t; - -// 7.8.1 Macros for format specifiers - -#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 - -// The fprintf macros for signed integers are: -#define PRId8 "d" -#define PRIi8 "i" -#define PRIdLEAST8 "d" -#define PRIiLEAST8 "i" -#define PRIdFAST8 "d" -#define PRIiFAST8 "i" - -#define PRId16 "hd" -#define PRIi16 "hi" -#define PRIdLEAST16 "hd" -#define PRIiLEAST16 "hi" -#define PRIdFAST16 "hd" -#define PRIiFAST16 "hi" - -#define PRId32 "I32d" -#define PRIi32 "I32i" -#define PRIdLEAST32 "I32d" -#define PRIiLEAST32 "I32i" -#define PRIdFAST32 "I32d" -#define PRIiFAST32 "I32i" - -#define PRId64 "I64d" -#define PRIi64 "I64i" -#define PRIdLEAST64 "I64d" -#define PRIiLEAST64 "I64i" -#define PRIdFAST64 "I64d" -#define PRIiFAST64 "I64i" - -#define PRIdMAX "I64d" -#define PRIiMAX "I64i" - -#define PRIdPTR "Id" -#define PRIiPTR "Ii" - -// The fprintf macros for unsigned integers are: -#define PRIo8 "o" -#define PRIu8 "u" -#define PRIx8 "x" -#define PRIX8 "X" -#define PRIoLEAST8 "o" -#define PRIuLEAST8 "u" -#define PRIxLEAST8 "x" -#define PRIXLEAST8 "X" -#define PRIoFAST8 "o" -#define PRIuFAST8 "u" -#define PRIxFAST8 "x" -#define PRIXFAST8 "X" - -#define PRIo16 "ho" -#define PRIu16 "hu" -#define PRIx16 "hx" -#define PRIX16 "hX" -#define PRIoLEAST16 "ho" -#define PRIuLEAST16 "hu" -#define PRIxLEAST16 "hx" -#define PRIXLEAST16 "hX" -#define PRIoFAST16 "ho" -#define PRIuFAST16 "hu" -#define PRIxFAST16 "hx" -#define PRIXFAST16 "hX" - -#define PRIo32 "I32o" -#define PRIu32 "I32u" -#define PRIx32 "I32x" -#define PRIX32 "I32X" -#define PRIoLEAST32 "I32o" -#define PRIuLEAST32 "I32u" -#define PRIxLEAST32 "I32x" -#define PRIXLEAST32 "I32X" -#define PRIoFAST32 "I32o" -#define PRIuFAST32 "I32u" -#define PRIxFAST32 "I32x" -#define PRIXFAST32 "I32X" - -#define PRIo64 "I64o" -#define PRIu64 "I64u" -#define PRIx64 "I64x" -#define PRIX64 "I64X" -#define PRIoLEAST64 "I64o" -#define PRIuLEAST64 "I64u" -#define PRIxLEAST64 "I64x" -#define PRIXLEAST64 "I64X" -#define PRIoFAST64 "I64o" -#define PRIuFAST64 "I64u" -#define PRIxFAST64 "I64x" -#define PRIXFAST64 "I64X" - -#define PRIoMAX "I64o" -#define PRIuMAX "I64u" -#define PRIxMAX "I64x" -#define PRIXMAX "I64X" - -#define PRIoPTR "Io" -#define PRIuPTR "Iu" -#define PRIxPTR "Ix" -#define PRIXPTR "IX" - -// The fscanf macros for signed integers are: -#define SCNd8 "d" -#define SCNi8 "i" -#define SCNdLEAST8 "d" -#define SCNiLEAST8 "i" -#define SCNdFAST8 "d" -#define SCNiFAST8 "i" - -#define SCNd16 "hd" -#define SCNi16 "hi" -#define SCNdLEAST16 "hd" -#define SCNiLEAST16 "hi" -#define SCNdFAST16 "hd" -#define SCNiFAST16 "hi" - -#define SCNd32 "ld" -#define SCNi32 "li" -#define SCNdLEAST32 "ld" -#define SCNiLEAST32 "li" -#define SCNdFAST32 "ld" -#define SCNiFAST32 "li" - -#define SCNd64 "I64d" -#define SCNi64 "I64i" -#define SCNdLEAST64 "I64d" -#define SCNiLEAST64 "I64i" -#define SCNdFAST64 "I64d" -#define SCNiFAST64 "I64i" - -#define SCNdMAX "I64d" -#define SCNiMAX "I64i" - -#ifdef _WIN64 // [ -# define SCNdPTR "I64d" -# define SCNiPTR "I64i" -#else // _WIN64 ][ -# define SCNdPTR "ld" -# define SCNiPTR "li" -#endif // _WIN64 ] - -// The fscanf macros for unsigned integers are: -#define SCNo8 "o" -#define SCNu8 "u" -#define SCNx8 "x" -#define SCNX8 "X" -#define SCNoLEAST8 "o" -#define SCNuLEAST8 "u" -#define SCNxLEAST8 "x" -#define SCNXLEAST8 "X" -#define SCNoFAST8 "o" -#define SCNuFAST8 "u" -#define SCNxFAST8 "x" -#define SCNXFAST8 "X" - -#define SCNo16 "ho" -#define SCNu16 "hu" -#define SCNx16 "hx" -#define SCNX16 "hX" -#define SCNoLEAST16 "ho" -#define SCNuLEAST16 "hu" -#define SCNxLEAST16 "hx" -#define SCNXLEAST16 "hX" -#define SCNoFAST16 "ho" -#define SCNuFAST16 "hu" -#define SCNxFAST16 "hx" -#define SCNXFAST16 "hX" - -#define SCNo32 "lo" -#define SCNu32 "lu" -#define SCNx32 "lx" -#define SCNX32 "lX" -#define SCNoLEAST32 "lo" -#define SCNuLEAST32 "lu" -#define SCNxLEAST32 "lx" -#define SCNXLEAST32 "lX" -#define SCNoFAST32 "lo" -#define SCNuFAST32 "lu" -#define SCNxFAST32 "lx" -#define SCNXFAST32 "lX" - -#define SCNo64 "I64o" -#define SCNu64 "I64u" -#define SCNx64 "I64x" -#define SCNX64 "I64X" -#define SCNoLEAST64 "I64o" -#define SCNuLEAST64 "I64u" -#define SCNxLEAST64 "I64x" -#define SCNXLEAST64 "I64X" -#define SCNoFAST64 "I64o" -#define SCNuFAST64 "I64u" -#define SCNxFAST64 "I64x" -#define SCNXFAST64 "I64X" - -#define SCNoMAX "I64o" -#define SCNuMAX "I64u" -#define SCNxMAX "I64x" -#define SCNXMAX "I64X" - -#ifdef _WIN64 // [ -# define SCNoPTR "I64o" -# define SCNuPTR "I64u" -# define SCNxPTR "I64x" -# define SCNXPTR "I64X" -#else // _WIN64 ][ -# define SCNoPTR "lo" -# define SCNuPTR "lu" -# define SCNxPTR "lx" -# define SCNXPTR "lX" -#endif // _WIN64 ] - -#endif // __STDC_FORMAT_MACROS ] - -// 7.8.2 Functions for greatest-width integer types - -// 7.8.2.1 The imaxabs function -#define imaxabs _abs64 - -// 7.8.2.2 The imaxdiv function - -// This is modified version of div() function from Microsoft's div.c found -// in %MSVC.NET%\crt\src\div.c -#ifdef STATIC_IMAXDIV // [ -static -#else // STATIC_IMAXDIV ][ -_inline -#endif // STATIC_IMAXDIV ] -imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) -{ - imaxdiv_t result; - - result.quot = numer / denom; - result.rem = numer % denom; - - if (numer < 0 && result.rem > 0) { - // did division wrong; must fix up - ++result.quot; - result.rem -= denom; - } - - return result; -} - -// 7.8.2.3 The strtoimax and strtoumax functions -#define strtoimax _strtoi64 -#define strtoumax _strtoui64 - -// 7.8.2.4 The wcstoimax and wcstoumax functions -#define wcstoimax _wcstoi64 -#define wcstoumax _wcstoui64 - - -#endif // _MSC_INTTYPES_H_ ] diff --git a/include/git2/merge.h b/include/git2/merge.h index 862721370..fc27c9d1d 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -57,7 +57,7 @@ typedef struct { * `GIT_MERGE_FILE_INPUT_VERSION` here. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_merge_file_init_input( +GIT_EXTERN(int) git_merge_file_input_init( git_merge_file_input *opts, unsigned int version); @@ -192,7 +192,7 @@ typedef struct { git_merge_file_favor_t favor; /** see `git_merge_file_flag_t` above */ - git_merge_file_flag_t flags; + uint32_t flags; /** The size of conflict markers (eg, "<<<<<<<"). Default is * GIT_MERGE_CONFLICT_MARKER_SIZE. */ @@ -212,9 +212,7 @@ typedef struct { * @param version The struct version; pass `GIT_MERGE_FILE_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_merge_file_init_options( - git_merge_file_options *opts, - unsigned int version); +GIT_EXTERN(int) git_merge_file_options_init(git_merge_file_options *opts, unsigned int version); /** * Information about file-level merging @@ -249,7 +247,7 @@ typedef struct { unsigned int version; /** See `git_merge_flag_t` above */ - git_merge_flag_t flags; + uint32_t flags; /** * Similarity to consider a file renamed (default 50). If @@ -293,7 +291,7 @@ typedef struct { git_merge_file_favor_t file_favor; /** see `git_merge_file_flag_t` above */ - git_merge_file_flag_t file_flags; + uint32_t file_flags; } git_merge_options; #define GIT_MERGE_OPTIONS_VERSION 1 @@ -310,9 +308,7 @@ typedef struct { * @param version The struct version; pass `GIT_MERGE_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_merge_init_options( - git_merge_options *opts, - unsigned int version); +GIT_EXTERN(int) git_merge_options_init(git_merge_options *opts, unsigned int version); /** * The results of `git_merge_analysis` indicate the merge opportunities. diff --git a/include/git2/net.h b/include/git2/net.h index 391cdee1c..8103eafbf 100644 --- a/include/git2/net.h +++ b/include/git2/net.h @@ -49,11 +49,6 @@ struct git_remote_head { char *symref_target; }; -/** - * Callback for listing the remote heads - */ -typedef int GIT_CALLBACK(git_headlist_cb)(git_remote_head *rhead, void *payload); - /** @} */ GIT_END_DECL #endif diff --git a/include/git2/object.h b/include/git2/object.h index 01dc37ace..984dbb7ac 100644 --- a/include/git2/object.h +++ b/include/git2/object.h @@ -21,6 +21,8 @@ */ GIT_BEGIN_DECL +#define GIT_OBJECT_SIZE_MAX UINT64_MAX + /** * Lookup a reference to one of the objects in a repository. * @@ -185,20 +187,6 @@ GIT_EXTERN(git_object_t) git_object_string2type(const char *str); */ GIT_EXTERN(int) git_object_typeisloose(git_object_t type); -/** - * Get the size in bytes for the structure which - * acts as an in-memory representation of any given - * object type. - * - * For all the core types, this would the equivalent - * of calling `sizeof(git_commit)` if the core types - * were not opaque on the external API. - * - * @param type object type to get its size - * @return size in bytes of the object - */ -GIT_EXTERN(size_t) git_object__size(git_object_t type); - /** * Recursively peel an object until an object of the specified type is met. * diff --git a/include/git2/odb.h b/include/git2/odb.h index 0008eee20..c4bfa5290 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -11,6 +11,7 @@ #include "types.h" #include "oid.h" #include "oidarray.h" +#include "indexer.h" /** * @file git2/odb.h @@ -293,7 +294,7 @@ GIT_EXTERN(int) git_odb_write(git_oid *out, git_odb *odb, const void *data, size * @param type type of the object that will be written * @return 0 if the stream was created; error code otherwise */ -GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, git_off_t size, git_object_t type); +GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, git_object_size_t size, git_object_t type); /** * Write to an odb stream @@ -391,7 +392,7 @@ GIT_EXTERN(int) git_odb_open_rstream( GIT_EXTERN(int) git_odb_write_pack( git_odb_writepack **out, git_odb *db, - git_transfer_progress_cb progress_cb, + git_indexer_progress_cb progress_cb, void *progress_payload); /** diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index 614d0d431..c593bac26 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -9,6 +9,7 @@ #include "common.h" #include "types.h" +#include "indexer.h" /** * @file git2/backend.h @@ -86,8 +87,8 @@ struct git_odb_stream { unsigned int mode; void *hash_ctx; - git_off_t declared_size; - git_off_t received_bytes; + git_object_size_t declared_size; + git_object_size_t received_bytes; /** * Write at most `len` bytes into `buffer` and advance the stream. @@ -124,8 +125,8 @@ struct git_odb_stream { struct git_odb_writepack { git_odb_backend *backend; - int GIT_CALLBACK(append)(git_odb_writepack *writepack, const void *data, size_t size, git_transfer_progress *stats); - int GIT_CALLBACK(commit)(git_odb_writepack *writepack, git_transfer_progress *stats); + int GIT_CALLBACK(append)(git_odb_writepack *writepack, const void *data, size_t size, git_indexer_progress *stats); + int GIT_CALLBACK(commit)(git_odb_writepack *writepack, git_indexer_progress *stats); void GIT_CALLBACK(free)(git_odb_writepack *writepack); }; diff --git a/include/git2/oid.h b/include/git2/oid.h index 7e071bac7..549df4eab 100644 --- a/include/git2/oid.h +++ b/include/git2/oid.h @@ -73,8 +73,9 @@ GIT_EXTERN(int) git_oid_fromstrn(git_oid *out, const char *str, size_t length); * * @param out oid structure the result is written into. * @param raw the raw input bytes to be copied. + * @return 0 on success or error code */ -GIT_EXTERN(void) git_oid_fromraw(git_oid *out, const unsigned char *raw); +GIT_EXTERN(int) git_oid_fromraw(git_oid *out, const unsigned char *raw); /** * Format a git_oid into a hex string. @@ -85,8 +86,9 @@ GIT_EXTERN(void) git_oid_fromraw(git_oid *out, const unsigned char *raw); * oid digits are written; a '\\0' terminator must be added * by the caller if it is required. * @param id oid structure to format. + * @return 0 on success or error code */ -GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id); +GIT_EXTERN(int) git_oid_fmt(char *out, const git_oid *id); /** * Format a git_oid into a partial hex string. @@ -96,8 +98,9 @@ GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id); * will be zeroed; if not, a '\0' terminator is NOT added. * @param n number of characters to write into out string * @param id oid structure to format. + * @return 0 on success or error code */ -GIT_EXTERN(void) git_oid_nfmt(char *out, size_t n, const git_oid *id); +GIT_EXTERN(int) git_oid_nfmt(char *out, size_t n, const git_oid *id); /** * Format a git_oid into a loose-object path string. @@ -111,8 +114,9 @@ GIT_EXTERN(void) git_oid_nfmt(char *out, size_t n, const git_oid *id); * oid digits are written; a '\\0' terminator must be added * by the caller if it is required. * @param id oid structure to format. + * @return 0 on success, non-zero callback return value, or error code */ -GIT_EXTERN(void) git_oid_pathfmt(char *out, const git_oid *id); +GIT_EXTERN(int) git_oid_pathfmt(char *out, const git_oid *id); /** * Format a git_oid into a statically allocated c-string. @@ -151,8 +155,9 @@ GIT_EXTERN(char *) git_oid_tostr(char *out, size_t n, const git_oid *id); * * @param out oid structure the result is written into. * @param src oid structure to copy from. + * @return 0 on success or error code */ -GIT_EXTERN(void) git_oid_cpy(git_oid *out, const git_oid *src); +GIT_EXTERN(int) git_oid_cpy(git_oid *out, const git_oid *src); /** * Compare two oid structures. @@ -207,7 +212,7 @@ GIT_EXTERN(int) git_oid_strcmp(const git_oid *id, const char *str); * * @return 1 if all zeros, 0 otherwise. */ -GIT_EXTERN(int) git_oid_iszero(const git_oid *id); +GIT_EXTERN(int) git_oid_is_zero(const git_oid *id); /** * OID Shortener object diff --git a/include/git2/pack.h b/include/git2/pack.h index beb9f4b9e..922a3cd9d 100644 --- a/include/git2/pack.h +++ b/include/git2/pack.h @@ -9,6 +9,7 @@ #include "common.h" #include "oid.h" +#include "indexer.h" /** * @file git2/pack.h @@ -165,7 +166,7 @@ GIT_EXTERN(int) git_packbuilder_write( git_packbuilder *pb, const char *path, unsigned int mode, - git_transfer_progress_cb progress_cb, + git_indexer_progress_cb progress_cb, void *progress_cb_payload); /** @@ -178,6 +179,16 @@ GIT_EXTERN(int) git_packbuilder_write( */ GIT_EXTERN(const git_oid *) git_packbuilder_hash(git_packbuilder *pb); +/** + * Callback used to iterate over packed objects + * + * @see git_packbuilder_foreach + * + * @param buf A pointer to the object's data + * @param size The size of the underlying object + * @param payload Payload passed to git_packbuilder_foreach + * @return non-zero to terminate the iteration + */ typedef int GIT_CALLBACK(git_packbuilder_foreach_cb)(void *buf, size_t size, void *payload); /** diff --git a/include/git2/proxy.h b/include/git2/proxy.h index 5f9d27f30..653425dee 100644 --- a/include/git2/proxy.h +++ b/include/git2/proxy.h @@ -8,7 +8,9 @@ #define INCLUDE_git_proxy_h__ #include "common.h" -#include "transport.h" + +#include "cert.h" +#include "credential.h" GIT_BEGIN_DECL @@ -59,7 +61,7 @@ typedef struct { * Returning GIT_PASSTHROUGH will make libgit2 behave as * though this field isn't set. */ - git_cred_acquire_cb credentials; + git_credential_acquire_cb credentials; /** * If cert verification fails, this will be called to let the @@ -67,7 +69,7 @@ typedef struct { * connection to proceed. Returns 0 to allow the connection * or a negative value to indicate an error. */ - git_transport_certificate_check_cb certificate_check; + git_transport_certificate_check_cb certificate_check; /** * Payload to be provided to the credentials and certificate @@ -89,7 +91,7 @@ typedef struct { * @param version The struct version; pass `GIT_PROXY_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_proxy_init_options(git_proxy_options *opts, unsigned int version); +GIT_EXTERN(int) git_proxy_options_init(git_proxy_options *opts, unsigned int version); GIT_END_DECL diff --git a/include/git2/rebase.h b/include/git2/rebase.h index f6b2e20ad..99a02fef9 100644 --- a/include/git2/rebase.h +++ b/include/git2/rebase.h @@ -13,6 +13,7 @@ #include "annotated_commit.h" #include "merge.h" #include "checkout.h" +#include "commit.h" /** * @file git2/rebase.h @@ -72,6 +73,21 @@ typedef struct { * `abort` to match git semantics. */ git_checkout_options checkout_options; + + /** + * If provided, this will be called with the commit content, allowing + * a signature to be added to the rebase commit. Can be skipped with + * GIT_PASSTHROUGH. If GIT_PASSTHROUGH is returned, a commit will be made + * without a signature. + * This field is only used when performing git_rebase_commit. + */ + git_commit_signing_cb signing_cb; + + /** + * This will be passed to each of the callbacks in this struct + * as the last parameter. + */ + void *payload; } git_rebase_options; /** @@ -118,7 +134,7 @@ typedef enum { #define GIT_REBASE_OPTIONS_VERSION 1 #define GIT_REBASE_OPTIONS_INIT \ { GIT_REBASE_OPTIONS_VERSION, 0, 0, NULL, GIT_MERGE_OPTIONS_INIT, \ - GIT_CHECKOUT_OPTIONS_INIT} + GIT_CHECKOUT_OPTIONS_INIT, NULL, NULL } /** Indicates that a rebase operation is not (yet) in progress. */ #define GIT_REBASE_NO_OPERATION SIZE_MAX @@ -156,7 +172,7 @@ typedef struct { * @param version The struct version; pass `GIT_REBASE_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_rebase_init_options( +GIT_EXTERN(int) git_rebase_options_init( git_rebase_options *opts, unsigned int version); @@ -199,6 +215,34 @@ GIT_EXTERN(int) git_rebase_open( git_repository *repo, const git_rebase_options *opts); +/** + * Gets the original `HEAD` ref name for merge rebases. + * + * @return The original `HEAD` ref name + */ +GIT_EXTERN(const char *) git_rebase_orig_head_name(git_rebase *rebase); + +/** + * Gets the original `HEAD` id for merge rebases. + * + * @return The original `HEAD` id + */ +GIT_EXTERN(const git_oid *) git_rebase_orig_head_id(git_rebase *rebase); + +/** + * Gets the `onto` ref name for merge rebases. + * + * @return The `onto` ref name + */ +GIT_EXTERN(const char *) git_rebase_onto_name(git_rebase *rebase); + +/** + * Gets the `onto` id for merge rebases. + * + * @return The `onto` id + */ +GIT_EXTERN(const git_oid *) git_rebase_onto_id(git_rebase *rebase); + /** * Gets the count of rebase operations that are to be applied. * diff --git a/include/git2/refs.h b/include/git2/refs.h index 8eeab7e92..c9cce2212 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -422,7 +422,26 @@ GIT_EXTERN(int) git_reference_remove(git_repository *repo, const char *name); */ GIT_EXTERN(int) git_reference_list(git_strarray *array, git_repository *repo); +/** + * Callback used to iterate over references + * + * @see git_reference_foreach + * + * @param reference The reference object + * @param payload Payload passed to git_reference_foreach + * @return non-zero to terminate the iteration + */ typedef int GIT_CALLBACK(git_reference_foreach_cb)(git_reference *reference, void *payload); + +/** + * Callback used to iterate over reference names + * + * @see git_reference_foreach_name + * + * @param name The reference name + * @param payload Payload passed to git_reference_foreach_name + * @return non-zero to terminate the iteration + */ typedef int GIT_CALLBACK(git_reference_foreach_name_cb)(const char *name, void *payload); /** diff --git a/include/git2/remote.h b/include/git2/remote.h index 0b6e92a9e..51d9c7235 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -56,7 +56,7 @@ typedef enum { * Remote creation options structure * * Initialize with `GIT_REMOTE_CREATE_OPTIONS_INIT`. Alternatively, you can - * use `git_remote_create_init_options`. + * use `git_remote_create_options_init`. * */ typedef struct git_remote_create_options { @@ -94,7 +94,7 @@ typedef struct git_remote_create_options { * @param version The struct version; pass `GIT_REMOTE_CREATE_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_remote_create_init_options( +GIT_EXTERN(int) git_remote_create_options_init( git_remote_create_options *opts, unsigned int version); @@ -378,8 +378,9 @@ GIT_EXTERN(int) git_remote_connected(const git_remote *remote); * the operation has been cancelled and if so stops the operation. * * @param remote the remote + * @return 0 on success, or an error code */ -GIT_EXTERN(void) git_remote_stop(git_remote *remote); +GIT_EXTERN(int) git_remote_stop(git_remote *remote); /** * Disconnect from the remote @@ -387,8 +388,9 @@ GIT_EXTERN(void) git_remote_stop(git_remote *remote); * Close the connection to the remote. * * @param remote the remote to disconnect from + * @return 0 on success, or an error code */ -GIT_EXTERN(void) git_remote_disconnect(git_remote *remote); +GIT_EXTERN(int) git_remote_disconnect(git_remote *remote); /** * Free the memory associated with a remote @@ -415,18 +417,19 @@ GIT_EXTERN(int) git_remote_list(git_strarray *out, git_repository *repo); * Argument to the completion callback which tells it which operation * finished. */ -typedef enum git_remote_completion_type { +typedef enum git_remote_completion_t { GIT_REMOTE_COMPLETION_DOWNLOAD, GIT_REMOTE_COMPLETION_INDEXING, GIT_REMOTE_COMPLETION_ERROR, -} git_remote_completion_type; +} git_remote_completion_t; /** Push network progress notification function */ -typedef int GIT_CALLBACK(git_push_transfer_progress)( +typedef int GIT_CALLBACK(git_push_transfer_progress_cb)( unsigned int current, unsigned int total, size_t bytes, void* payload); + /** * Represents an update which will be performed on the remote during push */ @@ -473,6 +476,20 @@ typedef int GIT_CALLBACK(git_push_negotiation)(const git_push_update **updates, */ typedef int GIT_CALLBACK(git_push_update_reference_cb)(const char *refname, const char *status, void *data); +/** + * Callback to resolve URLs before connecting to remote + * + * If you return GIT_PASSTHROUGH, you don't need to write anything to + * url_resolved. + * + * @param url_resolved The buffer to write the resolved URL to + * @param url The URL to resolve + * @param direction GIT_DIRECTION_FETCH or GIT_DIRECTION_PUSH + * @param payload Payload provided by the caller + * @return 0 on success, GIT_PASSTHROUGH or an error + */ +typedef int GIT_CALLBACK(git_url_resolve_cb)(git_buf *url_resolved, const char *url, int direction, void *payload); + /** * The callback settings structure * @@ -480,7 +497,8 @@ typedef int GIT_CALLBACK(git_push_update_reference_cb)(const char *refname, cons * about the progress of the network operations. */ struct git_remote_callbacks { - unsigned int version; + unsigned int version; /**< The version */ + /** * Textual progress from the remote. Text send over the * progress side-band will be passed to this function (this is @@ -492,7 +510,7 @@ struct git_remote_callbacks { * Completion is called when different parts of the download * process are done (currently unused). */ - int GIT_CALLBACK(completion)(git_remote_completion_type type, void *data); + int GIT_CALLBACK(completion)(git_remote_completion_t type, void *data); /** * This will be called if the remote host requires @@ -501,7 +519,7 @@ struct git_remote_callbacks { * Returning GIT_PASSTHROUGH will make libgit2 behave as * though this field isn't set. */ - git_cred_acquire_cb credentials; + git_credential_acquire_cb credentials; /** * If cert verification fails, this will be called to let the @@ -516,7 +534,7 @@ struct git_remote_callbacks { * called with the current count of progress done by the * indexer. */ - git_transfer_progress_cb transfer_progress; + git_indexer_progress_cb transfer_progress; /** * Each time a reference is updated locally, this function @@ -537,7 +555,7 @@ struct git_remote_callbacks { * inline with pack building operations, so performance may be * affected. */ - git_push_transfer_progress push_transfer_progress; + git_push_transfer_progress_cb push_transfer_progress; /** * See documentation of git_push_update_reference_cb @@ -561,6 +579,12 @@ struct git_remote_callbacks { * as the last parameter. */ void *payload; + + /** + * Resolve URL before connecting to remote. + * The returned URL will be used to connect to the remote instead. + */ + git_url_resolve_cb resolve_url; }; #define GIT_REMOTE_CALLBACKS_VERSION 1 @@ -578,6 +602,7 @@ GIT_EXTERN(int) git_remote_init_callbacks( git_remote_callbacks *opts, unsigned int version); +/** Acceptable prune settings when fetching */ typedef enum { /** * Use the setting from the configuration @@ -679,7 +704,7 @@ typedef struct { * @param version The struct version; pass `GIT_FETCH_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_fetch_init_options( +GIT_EXTERN(int) git_fetch_options_init( git_fetch_options *opts, unsigned int version); @@ -729,7 +754,7 @@ typedef struct { * @param version The struct version; pass `GIT_PUSH_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_push_init_options( +GIT_EXTERN(int) git_push_options_init( git_push_options *opts, unsigned int version); @@ -832,7 +857,7 @@ GIT_EXTERN(int) git_remote_push(git_remote *remote, /** * Get the statistics structure that is filled in by the fetch operation. */ -GIT_EXTERN(const git_transfer_progress *) git_remote_stats(git_remote *remote); +GIT_EXTERN(const git_indexer_progress *) git_remote_stats(git_remote *remote); /** * Retrieve the tag auto-follow setting diff --git a/include/git2/repository.h b/include/git2/repository.h index 04c7300ce..9ddcd3404 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -323,7 +323,7 @@ typedef struct { * @param version The struct version; pass `GIT_REPOSITORY_INIT_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_repository_init_init_options( +GIT_EXTERN(int) git_repository_init_options_init( git_repository_init_options *opts, unsigned int version); @@ -438,7 +438,8 @@ typedef enum { GIT_REPOSITORY_ITEM_HOOKS, GIT_REPOSITORY_ITEM_LOGS, GIT_REPOSITORY_ITEM_MODULES, - GIT_REPOSITORY_ITEM_WORKTREES + GIT_REPOSITORY_ITEM_WORKTREES, + GIT_REPOSITORY_ITEM__LAST } git_repository_item_t; /** @@ -480,10 +481,11 @@ GIT_EXTERN(const char *) git_repository_path(const git_repository *repo); GIT_EXTERN(const char *) git_repository_workdir(const git_repository *repo); /** - * Get the path of the shared common directory for this repository - * - * If the repository is bare is not a worktree, the git directory - * path is returned. + * Get the path of the shared common directory for this repository. + * + * If the repository is bare, it is the root directory for the repository. + * If the repository is a worktree, it is the parent repo's gitdir. + * Otherwise, it is the gitdir. * * @param repo A repository object * @return the path to the common dir @@ -640,6 +642,18 @@ GIT_EXTERN(int) git_repository_message_remove(git_repository *repo); */ GIT_EXTERN(int) git_repository_state_cleanup(git_repository *repo); +/** + * Callback used to iterate over each FETCH_HEAD entry + * + * @see git_repository_fetchhead_foreach + * + * @param ref_name The reference name + * @param remote_url The remote URL + * @param oid The reference target OID + * @param is_merge Was the reference the result of a merge + * @param payload Payload passed to git_repository_fetchhead_foreach + * @return non-zero to terminate the iteration + */ typedef int GIT_CALLBACK(git_repository_fetchhead_foreach_cb)(const char *ref_name, const char *remote_url, const git_oid *oid, @@ -662,6 +676,15 @@ GIT_EXTERN(int) git_repository_fetchhead_foreach( git_repository_fetchhead_foreach_cb callback, void *payload); +/** + * Callback used to iterate over each MERGE_HEAD entry + * + * @see git_repository_mergehead_foreach + * + * @param oid The merge OID + * @param payload Payload passed to git_repository_mergehead_foreach + * @return non-zero to terminate the iteration + */ typedef int GIT_CALLBACK(git_repository_mergehead_foreach_cb)(const git_oid *oid, void *payload); diff --git a/include/git2/revert.h b/include/git2/revert.h index 260ad044b..331e90dff 100644 --- a/include/git2/revert.h +++ b/include/git2/revert.h @@ -46,7 +46,7 @@ typedef struct { * @param version The struct version; pass `GIT_REVERT_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_revert_init_options( +GIT_EXTERN(int) git_revert_options_init( git_revert_options *opts, unsigned int version); diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h index 9cb0d0a67..98dcbf8d1 100644 --- a/include/git2/revwalk.h +++ b/include/git2/revwalk.h @@ -84,8 +84,9 @@ GIT_EXTERN(int) git_revwalk_new(git_revwalk **out, git_repository *repo); * is over. * * @param walker handle to reset. + * @return 0 or an error code */ -GIT_EXTERN(void) git_revwalk_reset(git_revwalk *walker); +GIT_EXTERN(int) git_revwalk_reset(git_revwalk *walker); /** * Add a new root for the traversal @@ -224,8 +225,9 @@ GIT_EXTERN(int) git_revwalk_next(git_oid *out, git_revwalk *walk); * * @param walk the walker being used for the traversal. * @param sort_mode combination of GIT_SORT_XXX flags + * @return 0 or an error code */ -GIT_EXTERN(void) git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode); +GIT_EXTERN(int) git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode); /** * Push and hide the respective endpoints of the given range. @@ -246,8 +248,10 @@ GIT_EXTERN(int) git_revwalk_push_range(git_revwalk *walk, const char *range); * Simplify the history by first-parent * * No parents other than the first for each commit will be enqueued. + * + * @return 0 or an error code */ -GIT_EXTERN(void) git_revwalk_simplify_first_parent(git_revwalk *walk); +GIT_EXTERN(int) git_revwalk_simplify_first_parent(git_revwalk *walk); /** diff --git a/include/git2/stash.h b/include/git2/stash.h index c4dcf6845..625e51b4b 100644 --- a/include/git2/stash.h +++ b/include/git2/stash.h @@ -120,14 +120,14 @@ typedef int GIT_CALLBACK(git_stash_apply_progress_cb)( * Stash application options structure * * Initialize with `GIT_STASH_APPLY_OPTIONS_INIT`. Alternatively, you can - * use `git_stash_apply_init_options`. + * use `git_stash_apply_options_init`. * */ typedef struct git_stash_apply_options { unsigned int version; - /** See `git_stash_apply_flags_t`, above. */ - git_stash_apply_flags flags; + /** See `git_stash_apply_flags`, above. */ + uint32_t flags; /** Options to use when writing files to the working directory. */ git_checkout_options checkout_options; @@ -153,7 +153,7 @@ typedef struct git_stash_apply_options { * @param version The struct version; pass `GIT_STASH_APPLY_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_stash_apply_init_options( +GIT_EXTERN(int) git_stash_apply_options_init( git_stash_apply_options *opts, unsigned int version); /** diff --git a/include/git2/status.h b/include/git2/status.h index 06229ed2e..9693cc478 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -163,27 +163,36 @@ typedef enum { /** * Options to control how `git_status_foreach_ext()` will issue callbacks. * - * This structure is set so that zeroing it out will give you relatively - * sane defaults. + * Initialize with `GIT_STATUS_OPTIONS_INIT`. Alternatively, you can + * use `git_status_options_init`. * - * The `show` value is one of the `git_status_show_t` constants that - * control which files to scan and in what order. - * - * The `flags` value is an OR'ed combination of the `git_status_opt_t` - * values above. - * - * The `pathspec` is an array of path patterns to match (using - * fnmatch-style matching), or just an array of paths to match exactly if - * `GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH` is specified in the flags. - * - * The `baseline` is the tree to be used for comparison to the working directory - * and index; defaults to HEAD. */ typedef struct { - unsigned int version; + unsigned int version; /**< The version */ + + /** + * The `show` value is one of the `git_status_show_t` constants that + * control which files to scan and in what order. + */ git_status_show_t show; + + /** + * The `flags` value is an OR'ed combination of the `git_status_opt_t` + * values above. + */ unsigned int flags; + + /** + * The `pathspec` is an array of path patterns to match (using + * fnmatch-style matching), or just an array of paths to match exactly if + * `GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH` is specified in the flags. + */ git_strarray pathspec; + + /** + * The `baseline` is the tree to be used for comparison to the working directory + * and index; defaults to HEAD. + */ git_tree *baseline; } git_status_options; @@ -200,7 +209,7 @@ typedef struct { * @param version The struct version; pass `GIT_STATUS_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_status_init_options( +GIT_EXTERN(int) git_status_options_init( git_status_options *opts, unsigned int version); diff --git a/include/git2/submodule.h b/include/git2/submodule.h index efb3b75d6..bedd76d6a 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -122,7 +122,7 @@ typedef int GIT_CALLBACK(git_submodule_cb)( * Submodule update options structure * * Initialize with `GIT_SUBMODULE_UPDATE_OPTIONS_INIT`. Alternatively, you can - * use `git_submodule_update_init_options`. + * use `git_submodule_update_options_init`. * */ typedef struct git_submodule_update_options { @@ -168,7 +168,7 @@ typedef struct git_submodule_update_options { * @param version The struct version; pass `GIT_SUBMODULE_UPDATE_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_submodule_update_init_options( +GIT_EXTERN(int) git_submodule_update_options_init( git_submodule_update_options *opts, unsigned int version); /** @@ -263,7 +263,8 @@ GIT_EXTERN(int) git_submodule_foreach( * from the working directory to the new repo. * * To fully emulate "git submodule add" call this function, then open the - * submodule repo and perform the clone step as needed. Lastly, call + * submodule repo and perform the clone step as needed (if you don't need + * anything custom see `git_submodule_add_clone()`). Lastly, call * `git_submodule_add_finalize()` to wrap up adding the new submodule and * .gitmodules to the index to be ready to commit. * @@ -285,6 +286,22 @@ GIT_EXTERN(int) git_submodule_add_setup( const char *path, int use_gitlink); +/** + * Perform the clone step for a newly created submodule. + * + * This performs the necessary `git_clone` to setup a newly-created submodule. + * + * @param out The newly created repository object. Optional. + * @param submodule The submodule currently waiting for its clone. + * @param opts The options to use. + * + * @return 0 on success, -1 on other errors (see git_clone). + */ +GIT_EXTERN(int) git_submodule_clone( + git_repository **out, + git_submodule *submodule, + const git_submodule_update_options *opts); + /** * Resolve the setup of a new git submodule. * diff --git a/include/git2/sys/alloc.h b/include/git2/sys/alloc.h index 642740dab..c295807bc 100644 --- a/include/git2/sys/alloc.h +++ b/include/git2/sys/alloc.h @@ -21,51 +21,51 @@ GIT_BEGIN_DECL * that all fields need to be set to a proper function. */ typedef struct { - /* Allocate `n` bytes of memory */ + /** Allocate `n` bytes of memory */ void * GIT_CALLBACK(gmalloc)(size_t n, const char *file, int line); - /* + /** * Allocate memory for an array of `nelem` elements, where each element * has a size of `elsize`. Returned memory shall be initialized to * all-zeroes */ void * GIT_CALLBACK(gcalloc)(size_t nelem, size_t elsize, const char *file, int line); - /* Allocate memory for the string `str` and duplicate its contents. */ + /** Allocate memory for the string `str` and duplicate its contents. */ char * GIT_CALLBACK(gstrdup)(const char *str, const char *file, int line); - /* + /** * Equivalent to the `gstrdup` function, but only duplicating at most * `n + 1` bytes */ char * GIT_CALLBACK(gstrndup)(const char *str, size_t n, const char *file, int line); - /* + /** * Equivalent to `gstrndup`, but will always duplicate exactly `n` bytes * of `str`. Thus, out of bounds reads at `str` may happen. */ char * GIT_CALLBACK(gsubstrdup)(const char *str, size_t n, const char *file, int line); - /* + /** * This function shall deallocate the old object `ptr` and return a * pointer to a new object that has the size specified by `size`. In * case `ptr` is `NULL`, a new array shall be allocated. */ void * GIT_CALLBACK(grealloc)(void *ptr, size_t size, const char *file, int line); - /* + /** * This function shall be equivalent to `grealloc`, but allocating * `neleme * elsize` bytes. */ void * GIT_CALLBACK(greallocarray)(void *ptr, size_t nelem, size_t elsize, const char *file, int line); - /* + /** * This function shall allocate a new array of `nelem` elements, where * each element has a size of `elsize` bytes. */ void * GIT_CALLBACK(gmallocarray)(size_t nelem, size_t elsize, const char *file, int line); - /* + /** * This function shall free the memory pointed to by `ptr`. In case * `ptr` is `NULL`, this shall be a no-op. */ diff --git a/include/git2/sys/cred.h b/include/git2/sys/cred.h new file mode 100644 index 000000000..4d2a59af7 --- /dev/null +++ b/include/git2/sys/cred.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_sys_git_cred_h__ +#define INCLUDE_sys_git_cred_h__ + +/* These declarations have moved. */ +#ifndef GIT_DEPRECATE_HARD +# include "git2/sys/credential.h" +#endif + +#endif diff --git a/include/git2/sys/credential.h b/include/git2/sys/credential.h new file mode 100644 index 000000000..bb4c9f942 --- /dev/null +++ b/include/git2/sys/credential.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_sys_git_credential_h__ +#define INCLUDE_sys_git_credential_h__ + +#include "git2/common.h" +#include "git2/credential.h" + +/** + * @file git2/sys/cred.h + * @brief Git credentials low-level implementation + * @defgroup git_credential Git credentials low-level implementation + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * The base structure for all credential types + */ +struct git_credential { + git_credential_t credtype; /**< A type of credential */ + + /** The deallocator for this type of credentials */ + void GIT_CALLBACK(free)(git_credential *cred); +}; + +/** A plaintext username and password */ +struct git_credential_userpass_plaintext { + git_credential parent; /**< The parent credential */ + char *username; /**< The username to authenticate as */ + char *password; /**< The password to use */ +}; + +/** Username-only credential information */ +struct git_credential_username { + git_credential parent; /**< The parent credential */ + char username[1]; /**< The username to authenticate as */ +}; + +/** + * A ssh key from disk + */ +struct git_credential_ssh_key { + git_credential parent; /**< The parent credential */ + char *username; /**< The username to authenticate as */ + char *publickey; /**< The path to a public key */ + char *privatekey; /**< The path to a private key */ + char *passphrase; /**< Passphrase to decrypt the private key */ +}; + +/** + * Keyboard-interactive based ssh authentication + */ +struct git_credential_ssh_interactive { + git_credential parent; /**< The parent credential */ + char *username; /**< The username to authenticate as */ + + /** + * Callback used for authentication. + */ + git_credential_ssh_interactive_cb prompt_callback; + + void *payload; /**< Payload passed to prompt_callback */ +}; + +/** + * A key with a custom signature function + */ +struct git_credential_ssh_custom { + git_credential parent; /**< The parent credential */ + char *username; /**< The username to authenticate as */ + char *publickey; /**< The public key data */ + size_t publickey_len; /**< Length of the public key */ + + /** + * Callback used to sign the data. + */ + git_credential_sign_cb sign_callback; + + void *payload; /**< Payload passed to prompt_callback */ +}; + +GIT_END_DECL + +#endif diff --git a/include/git2/sys/index.h b/include/git2/sys/index.h index f1900df42..1f6d93f7a 100644 --- a/include/git2/sys/index.h +++ b/include/git2/sys/index.h @@ -75,8 +75,9 @@ GIT_EXTERN(int) git_index_name_add(git_index *index, * Remove all filename conflict entries. * * @param index an existing index object + * @return 0 or an error code */ -GIT_EXTERN(void) git_index_name_clear(git_index *index); +GIT_EXTERN(int) git_index_name_clear(git_index *index); /**@}*/ @@ -170,8 +171,9 @@ GIT_EXTERN(int) git_index_reuc_remove(git_index *index, size_t n); * Remove all resolve undo entries from the index * * @param index an existing index object + * @return 0 or an error code */ -GIT_EXTERN(void) git_index_reuc_clear(git_index *index); +GIT_EXTERN(int) git_index_reuc_clear(git_index *index); /**@}*/ diff --git a/include/git2/sys/mempack.h b/include/git2/sys/mempack.h index 63fb38dc8..17da590a3 100644 --- a/include/git2/sys/mempack.h +++ b/include/git2/sys/mempack.h @@ -78,8 +78,9 @@ GIT_EXTERN(int) git_mempack_dump(git_buf *pack, git_repository *repo, git_odb_ba * semantics to the Git repository. * * @param backend The mempack backend + * @return 0 on success; error code otherwise */ -GIT_EXTERN(void) git_mempack_reset(git_odb_backend *backend); +GIT_EXTERN(int) git_mempack_reset(git_odb_backend *backend); GIT_END_DECL diff --git a/include/git2/sys/merge.h b/include/git2/sys/merge.h index bd0a8a4b7..ef4bc5aca 100644 --- a/include/git2/sys/merge.h +++ b/include/git2/sys/merge.h @@ -41,7 +41,7 @@ GIT_EXTERN(git_merge_driver *) git_merge_driver_lookup(const char *name); typedef struct git_merge_driver_source git_merge_driver_source; /** Get the repository that the source data is coming from. */ -GIT_EXTERN(const git_repository *) git_merge_driver_source_repo( +GIT_EXTERN(git_repository *) git_merge_driver_source_repo( const git_merge_driver_source *src); /** Gets the ancestor of the file to merge. */ diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h index 1a747570d..4dba460af 100644 --- a/include/git2/sys/odb_backend.h +++ b/include/git2/sys/odb_backend.h @@ -30,8 +30,8 @@ struct git_odb_backend { /* read and read_prefix each return to libgit2 a buffer which * will be freed later. The buffer should be allocated using - * the function git_odb_backend_malloc to ensure that it can - * be safely freed later. */ + * the function git_odb_backend_data_alloc to ensure that libgit2 + * can safely free it later. */ int GIT_CALLBACK(read)( void **, size_t *, git_object_t *, git_odb_backend *, const git_oid *); @@ -53,7 +53,7 @@ struct git_odb_backend { git_odb_backend *, const git_oid *, const void *, size_t, git_object_t); int GIT_CALLBACK(writestream)( - git_odb_stream **, git_odb_backend *, git_off_t, git_object_t); + git_odb_stream **, git_odb_backend *, git_object_size_t, git_object_t); int GIT_CALLBACK(readstream)( git_odb_stream **, size_t *, git_object_t *, @@ -82,7 +82,7 @@ struct git_odb_backend { int GIT_CALLBACK(writepack)( git_odb_writepack **, git_odb_backend *, git_odb *odb, - git_transfer_progress_cb progress_cb, void *progress_payload); + git_indexer_progress_cb progress_cb, void *progress_payload); /** * "Freshens" an already existing object, updating its last-used @@ -117,8 +117,52 @@ GIT_EXTERN(int) git_odb_init_backend( git_odb_backend *backend, unsigned int version); +/** + * Allocate data for an ODB object. Custom ODB backends may use this + * to provide data back to the ODB from their read function. This + * memory should not be freed once it is returned to libgit2. If a + * custom ODB uses this function but encounters an error and does not + * return this data to libgit2, then they should use the corresponding + * git_odb_backend_data_free function. + * + * @param backend the ODB backend that is allocating this memory + * @param len the number of bytes to allocate + * @return the allocated buffer on success or NULL if out of memory + */ +GIT_EXTERN(void *) git_odb_backend_data_alloc(git_odb_backend *backend, size_t len); + +/** + * Frees custom allocated ODB data. This should only be called when + * memory allocated using git_odb_backend_data_alloc is not returned + * to libgit2 because the backend encountered an error in the read + * function after allocation and did not return this data to libgit2. + * + * @param backend the ODB backend that is freeing this memory + * @param data the buffer to free + */ +GIT_EXTERN(void) git_odb_backend_data_free(git_odb_backend *backend, void *data); + + +/* + * Users can avoid deprecated functions by defining `GIT_DEPRECATE_HARD`. + */ +#ifndef GIT_DEPRECATE_HARD + +/** + * Allocate memory for an ODB object from a custom backend. This is + * an alias of `git_odb_backend_data_alloc` and is preserved for + * backward compatibility. + * + * This function is deprecated, but there is no plan to remove this + * function at this time. + * + * @deprecated git_odb_backend_data_alloc + * @see git_odb_backend_data_alloc + */ GIT_EXTERN(void *) git_odb_backend_malloc(git_odb_backend *backend, size_t len); +#endif + GIT_END_DECL #endif diff --git a/include/git2/sys/refdb_backend.h b/include/git2/sys/refdb_backend.h index 2ed6efd5a..c31e26d95 100644 --- a/include/git2/sys/refdb_backend.h +++ b/include/git2/sys/refdb_backend.h @@ -58,11 +58,18 @@ struct git_reference_iterator { /** An instance for a custom backend */ struct git_refdb_backend { - unsigned int version; + unsigned int version; /**< The backend API version */ /** - * Queries the refdb backend to determine if the given ref_name - * exists. A refdb implementation must provide this function. + * Queries the refdb backend for the existence of a reference. + * + * A refdb implementation must provide this function. + * + * @arg exists The implementation shall set this to `0` if a ref does + * not exist, otherwise to `1`. + * @arg ref_name The reference's name that should be checked for + * existence. + * @return `0` on success, a negative error value code. */ int GIT_CALLBACK(exists)( int *exists, @@ -70,8 +77,16 @@ struct git_refdb_backend { const char *ref_name); /** - * Queries the refdb backend for a given reference. A refdb - * implementation must provide this function. + * Queries the refdb backend for a given reference. + * + * A refdb implementation must provide this function. + * + * @arg out The implementation shall set this to the allocated + * reference, if it could be found, otherwise to `NULL`. + * @arg ref_name The reference's name that should be checked for + * existence. + * @return `0` on success, `GIT_ENOTFOUND` if the reference does + * exist, otherwise a negative error code. */ int GIT_CALLBACK(lookup)( git_reference **out, @@ -82,88 +97,216 @@ struct git_refdb_backend { * Allocate an iterator object for the backend. * * A refdb implementation must provide this function. + * + * @arg out The implementation shall set this to the allocated + * reference iterator. A custom structure may be used with an + * embedded `git_reference_iterator` structure. Both `next` + * and `next_name` functions of `git_reference_iterator` need + * to be populated. + * @arg glob A pattern to filter references by. If given, the iterator + * shall only return references that match the glob when + * passed to `wildmatch`. + * @return `0` on success, otherwise a negative error code. */ int GIT_CALLBACK(iterator)( git_reference_iterator **iter, struct git_refdb_backend *backend, const char *glob); - /* - * Writes the given reference to the refdb. A refdb implementation - * must provide this function. + /** + * Writes the given reference to the refdb. + * + * A refdb implementation must provide this function. + * + * @arg ref The reference to persist. May either be a symbolic or + * direct reference. + * @arg force Whether to write the reference if a reference with the + * same name already exists. + * @arg who The person updating the reference. Shall be used to create + * a reflog entry. + * @arg message The message detailing what kind of reference update is + * performed. Shall be used to create a reflog entry. + * @arg old If not `NULL` and `force` is not set, then the + * implementation needs to ensure that the reference is currently at + * the given OID before writing the new value. If both `old` + * and `old_target` are `NULL`, then the reference should not + * exist at the point of writing. + * @arg old_target If not `NULL` and `force` is not set, then the + * implementation needs to ensure that the symbolic + * reference is currently at the given target before + * writing the new value. If both `old` and + * `old_target` are `NULL`, then the reference should + * not exist at the point of writing. + * @return `0` on success, otherwise a negative error code. */ int GIT_CALLBACK(write)(git_refdb_backend *backend, const git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old, const char *old_target); + /** + * Rename a reference in the refdb. + * + * A refdb implementation must provide this function. + * + * @arg out The implementation shall set this to the newly created + * reference or `NULL` on error. + * @arg old_name The current name of the reference that is to be renamed. + * @arg new_name The new name that the old reference shall be renamed to. + * @arg force Whether to write the reference if a reference with the + * target name already exists. + * @arg who The person updating the reference. Shall be used to create + * a reflog entry. + * @arg message The message detailing what kind of reference update is + * performed. Shall be used to create a reflog entry. + * @return `0` on success, otherwise a negative error code. + */ int GIT_CALLBACK(rename)( git_reference **out, git_refdb_backend *backend, const char *old_name, const char *new_name, int force, const git_signature *who, const char *message); /** - * Deletes the given reference (and if necessary its reflog) - * from the refdb. A refdb implementation must provide this - * function. + * Deletes the given reference from the refdb. + * + * If it exists, its reflog should be deleted as well. + * + * A refdb implementation must provide this function. + * + * @arg ref_name The name of the reference name that shall be deleted. + * @arg old_id If not `NULL` and `force` is not set, then the + * implementation needs to ensure that the reference is currently at + * the given OID before writing the new value. + * @arg old_target If not `NULL` and `force` is not set, then the + * implementation needs to ensure that the symbolic + * reference is currently at the given target before + * writing the new value. + * @return `0` on success, otherwise a negative error code. */ int GIT_CALLBACK(del)(git_refdb_backend *backend, const char *ref_name, const git_oid *old_id, const char *old_target); /** * Suggests that the given refdb compress or optimize its references. - * This mechanism is implementation specific. (For on-disk reference - * databases, this may pack all loose references.) A refdb - * implementation may provide this function; if it is not provided, - * nothing will be done. + * + * This mechanism is implementation specific. For on-disk reference + * databases, this may pack all loose references. + * + * A refdb implementation may provide this function; if it is not + * provided, nothing will be done. + * + * @return `0` on success a negative error code otherwise */ int GIT_CALLBACK(compress)(git_refdb_backend *backend); /** * Query whether a particular reference has a log (may be empty) + * + * Shall return 1 if it has a reflog, 0 it it doesn't and negative in + * case an error occurred. + * + * A refdb implementation must provide this function. + * + * @return `0` on success, `1` if the reflog for the given reference + * exists, a negative error code otherwise */ int GIT_CALLBACK(has_log)(git_refdb_backend *backend, const char *refname); /** * Make sure a particular reference will have a reflog which * will be appended to on writes. + * + * A refdb implementation must provide this function. + * + * @return `0` on success, a negative error code otherwise */ int GIT_CALLBACK(ensure_log)(git_refdb_backend *backend, const char *refname); /** * Frees any resources held by the refdb (including the `git_refdb_backend` - * itself). A refdb backend implementation must provide this function. + * itself). + * + * A refdb backend implementation must provide this function. */ void GIT_CALLBACK(free)(git_refdb_backend *backend); /** * Read the reflog for the given reference name. + * + * A refdb implementation must provide this function. + * + * @return `0` on success, a negative error code otherwise */ int GIT_CALLBACK(reflog_read)(git_reflog **out, git_refdb_backend *backend, const char *name); /** * Write a reflog to disk. + * + * A refdb implementation must provide this function. + * + * @arg reflog The complete reference log for a given reference. Note + * that this may contain entries that have already been + * written to disk. + * @return `0` on success, a negative error code otherwise */ int GIT_CALLBACK(reflog_write)(git_refdb_backend *backend, git_reflog *reflog); /** - * Rename a reflog + * Rename a reflog. + * + * A refdb implementation must provide this function. + * + * @arg old_name The name of old reference whose reflog shall be renamed from. + * @arg new_name The name of new reference whose reflog shall be renamed to. + * @return `0` on success, a negative error code otherwise */ int GIT_CALLBACK(reflog_rename)(git_refdb_backend *_backend, const char *old_name, const char *new_name); /** * Remove a reflog. + * + * A refdb implementation must provide this function. + * + * @arg name The name of the reference whose reflog shall be deleted. + * @return `0` on success, a negative error code otherwise */ int GIT_CALLBACK(reflog_delete)(git_refdb_backend *backend, const char *name); /** - * Lock a reference. The opaque parameter will be passed to the unlock function + * Lock a reference. + * + * A refdb implementation may provide this function; if it is not + * provided, the transaction API will fail to work. + * + * @arg payload_out Opaque parameter that will be passed verbosely to + * `unlock`. + * @arg refname Reference that shall be locked. + * @return `0` on success, a negative error code otherwise */ int GIT_CALLBACK(lock)(void **payload_out, git_refdb_backend *backend, const char *refname); /** - * Unlock a reference. Only one of target or symbolic_target - * will be set. success indicates whether to update the - * reference or discard the lock (if it's false) + * Unlock a reference. + * + * Only one of target or symbolic_target will be set. + * `success` will be true if the reference should be update, false if + * the lock must be discarded. + * + * A refdb implementation must provide this function if a `lock` + * implementation is provided. + * + * @arg payload The payload returned by `lock`. + * @arg success `1` if a reference should be updated, `2` if + * a reference should be deleted, `0` if the lock must be + * discarded. + * @arg update_reflog `1` in case the reflog should be updated, `0` + * otherwise. + * @arg ref The reference which should be unlocked. + * @arg who The person updating the reference. Shall be used to create + * a reflog entry in case `update_reflog` is set. + * @arg message The message detailing what kind of reference update is + * performed. Shall be used to create a reflog entry in + * case `update_reflog` is set. + * @return `0` on success, a negative error code otherwise */ int GIT_CALLBACK(unlock)(git_refdb_backend *backend, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message); diff --git a/include/git2/sys/repository.h b/include/git2/sys/repository.h index 0c9142143..892be6692 100644 --- a/include/git2/sys/repository.h +++ b/include/git2/sys/repository.h @@ -25,6 +25,10 @@ GIT_BEGIN_DECL * Note that this is only useful if you wish to associate the repository * with a non-filesystem-backed object database and config store. * + * Caveats: since this repository has no physical location, some systems + * can fail to function properly: locations under $GIT_DIR, $GIT_COMMON_DIR, + * or $GIT_INFO_DIR are impacted. + * * @param out The blank repository * @return 0 on success, or an error code */ @@ -39,9 +43,12 @@ GIT_EXTERN(int) git_repository_new(git_repository **out); * There's no need to call this function directly unless you're * trying to aggressively cleanup the repo before its * deallocation. `git_repository_free` already performs this operation - * before deallocation the repo. + * before deallocating the repo. + * + * @param repo The repository to clean up + * @return 0 on success, or an error code */ -GIT_EXTERN(void) git_repository__cleanup(git_repository *repo); +GIT_EXTERN(int) git_repository__cleanup(git_repository *repo); /** * Update the filesystem config settings for an open repository @@ -74,8 +81,9 @@ GIT_EXTERN(int) git_repository_reinit_filesystem( * * @param repo A repository object * @param config A Config object + * @return 0 on success, or an error code */ -GIT_EXTERN(void) git_repository_set_config(git_repository *repo, git_config *config); +GIT_EXTERN(int) git_repository_set_config(git_repository *repo, git_config *config); /** * Set the Object Database for this repository @@ -89,8 +97,9 @@ GIT_EXTERN(void) git_repository_set_config(git_repository *repo, git_config *con * * @param repo A repository object * @param odb An ODB object + * @return 0 on success, or an error code */ -GIT_EXTERN(void) git_repository_set_odb(git_repository *repo, git_odb *odb); +GIT_EXTERN(int) git_repository_set_odb(git_repository *repo, git_odb *odb); /** * Set the Reference Database Backend for this repository @@ -104,8 +113,9 @@ GIT_EXTERN(void) git_repository_set_odb(git_repository *repo, git_odb *odb); * * @param repo A repository object * @param refdb An refdb object + * @return 0 on success, or an error code */ -GIT_EXTERN(void) git_repository_set_refdb(git_repository *repo, git_refdb *refdb); +GIT_EXTERN(int) git_repository_set_refdb(git_repository *repo, git_refdb *refdb); /** * Set the index file for this repository @@ -119,8 +129,9 @@ GIT_EXTERN(void) git_repository_set_refdb(git_repository *repo, git_refdb *refdb * * @param repo A repository object * @param index An index object + * @return 0 on success, or an error code */ -GIT_EXTERN(void) git_repository_set_index(git_repository *repo, git_index *index); +GIT_EXTERN(int) git_repository_set_index(git_repository *repo, git_index *index); /** * Set a repository to be bare. diff --git a/include/git2/sys/time.h b/include/git2/sys/time.h deleted file mode 100644 index e4f87e6e1..000000000 --- a/include/git2/sys/time.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_git_time_h__ -#define INCLUDE_git_time_h__ - -#include "git2/common.h" - -GIT_BEGIN_DECL - -/** - * Return a monotonic time value, useful for measuring running time - * and setting up timeouts. - * - * The returned value is an arbitrary point in time -- it can only be - * used when comparing it to another `git_time_monotonic` call. - * - * The time is returned in seconds, with a decimal fraction that differs - * on accuracy based on the underlying system, but should be least - * accurate to Nanoseconds. - * - * This function cannot fail. - */ -GIT_EXTERN(double) git_time_monotonic(void); - -GIT_END_DECL -#endif - diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h index 2a2f3c19a..6cee42f54 100644 --- a/include/git2/sys/transport.h +++ b/include/git2/sys/transport.h @@ -55,7 +55,7 @@ struct git_transport { int GIT_CALLBACK(connect)( git_transport *transport, const char *url, - git_cred_acquire_cb cred_acquire_cb, + git_credential_acquire_cb cred_acquire_cb, void *cred_acquire_payload, const git_proxy_options *proxy_opts, int direction, @@ -98,8 +98,8 @@ struct git_transport { int GIT_CALLBACK(download_pack)( git_transport *transport, git_repository *repo, - git_transfer_progress *stats, - git_transfer_progress_cb progress_cb, + git_indexer_progress *stats, + git_indexer_progress_cb progress_cb, void *progress_payload); /** Checks to see if the transport is connected */ @@ -266,7 +266,7 @@ GIT_EXTERN(int) git_transport_smart_certificate_check(git_transport *transport, * refused to provide credentials and callers should behave as if no * callback was set), or < 0 for an error */ -GIT_EXTERN(int) git_transport_smart_credentials(git_cred **out, git_transport *transport, const char *user, int methods); +GIT_EXTERN(int) git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods); /** * Get a copy of the proxy options diff --git a/include/git2/tag.h b/include/git2/tag.h index 1ca334843..4e5fe1db1 100644 --- a/include/git2/tag.h +++ b/include/git2/tag.h @@ -217,7 +217,7 @@ GIT_EXTERN(int) git_tag_annotation_create( * @param force Overwrite existing tags * @return 0 on success; error code otherwise */ -GIT_EXTERN(int) git_tag_create_frombuffer( +GIT_EXTERN(int) git_tag_create_from_buffer( git_oid *oid, git_repository *repo, const char *buffer, @@ -317,7 +317,16 @@ GIT_EXTERN(int) git_tag_list_match( const char *pattern, git_repository *repo); - +/** + * Callback used to iterate over tag names + * + * @see git_tag_foreach + * + * @param name The tag name + * @param oid The tag's OID + * @param payload Payload passed to git_tag_foreach + * @return non-zero to terminate the iteration + */ typedef int GIT_CALLBACK(git_tag_foreach_cb)(const char *name, git_oid *oid, void *payload); /** diff --git a/include/git2/trace.h b/include/git2/trace.h index f8bbfb28c..8cee3a94e 100644 --- a/include/git2/trace.h +++ b/include/git2/trace.h @@ -49,7 +49,7 @@ typedef enum { /** * An instance for a tracing function */ -typedef void GIT_CALLBACK(git_trace_callback)(git_trace_level_t level, const char *msg); +typedef void GIT_CALLBACK(git_trace_cb)(git_trace_level_t level, const char *msg); /** * Sets the system tracing configuration to the specified level with the @@ -60,7 +60,7 @@ typedef void GIT_CALLBACK(git_trace_callback)(git_trace_level_t level, const cha * @param cb Function to call with trace data * @return 0 or an error code */ -GIT_EXTERN(int) git_trace_set(git_trace_level_t level, git_trace_callback cb); +GIT_EXTERN(int) git_trace_set(git_trace_level_t level, git_trace_cb cb); /** @} */ GIT_END_DECL diff --git a/include/git2/transport.h b/include/git2/transport.h index 2ba2d7ee6..fc99ce8f3 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -10,6 +10,8 @@ #include "indexer.h" #include "net.h" #include "types.h" +#include "cert.h" +#include "credential.h" /** * @file git2/transport.h @@ -20,352 +22,21 @@ */ GIT_BEGIN_DECL +/** + * Callback for messages recieved by the transport. + * + * Return a negative value to cancel the network operation. + * + * @param str The message from the transport + * @param len The length of the message + * @param payload Payload provided by the caller + */ +typedef int GIT_CALLBACK(git_transport_message_cb)(const char *str, int len, void *payload); + /** Signature of a function which creates a transport */ typedef int GIT_CALLBACK(git_transport_cb)(git_transport **out, git_remote *owner, void *param); -/** - * Type of SSH host fingerprint - */ -typedef enum { - /** MD5 is available */ - GIT_CERT_SSH_MD5 = (1 << 0), - /** SHA-1 is available */ - GIT_CERT_SSH_SHA1 = (1 << 1), -} git_cert_ssh_t; - -/** - * Hostkey information taken from libssh2 - */ -typedef struct { - git_cert parent; - - /** - * A hostkey type from libssh2, either - * `GIT_CERT_SSH_MD5` or `GIT_CERT_SSH_SHA1` - */ - git_cert_ssh_t type; - - /** - * Hostkey hash. If type has `GIT_CERT_SSH_MD5` set, this will - * have the MD5 hash of the hostkey. - */ - unsigned char hash_md5[16]; - - /** - * Hostkey hash. If type has `GIT_CERT_SSH_SHA1` set, this will - * have the SHA-1 hash of the hostkey. - */ - unsigned char hash_sha1[20]; -} git_cert_hostkey; - -/** - * X.509 certificate information - */ -typedef struct { - git_cert parent; - /** - * Pointer to the X.509 certificate data - */ - void *data; - /** - * Length of the memory block pointed to by `data`. - */ - size_t len; -} git_cert_x509; - -/* - *** Begin interface for credentials acquisition *** - */ - -/** - * Supported credential types - * - * This represents the various types of authentication methods supported by - * the library. - */ -typedef enum { - /** - * A vanilla user/password request - * @see git_cred_userpass_plaintext_new - */ - GIT_CREDTYPE_USERPASS_PLAINTEXT = (1u << 0), - - /** - * An SSH key-based authentication request - * @see git_cred_ssh_key_new - */ - GIT_CREDTYPE_SSH_KEY = (1u << 1), - - /** - * An SSH key-based authentication request, with a custom signature - * @see git_cred_ssh_custom_new - */ - GIT_CREDTYPE_SSH_CUSTOM = (1u << 2), - - /** - * An NTLM/Negotiate-based authentication request. - * @see git_cred_default - */ - GIT_CREDTYPE_DEFAULT = (1u << 3), - - /** - * An SSH interactive authentication request - * @see git_cred_ssh_interactive_new - */ - GIT_CREDTYPE_SSH_INTERACTIVE = (1u << 4), - - /** - * Username-only authentication request - * - * Used as a pre-authentication step if the underlying transport - * (eg. SSH, with no username in its URL) does not know which username - * to use. - * - * @see git_cred_username_new - */ - GIT_CREDTYPE_USERNAME = (1u << 5), - - /** - * An SSH key-based authentication request - * - * Allows credentials to be read from memory instead of files. - * Note that because of differences in crypto backend support, it might - * not be functional. - * - * @see git_cred_ssh_key_memory_new - */ - GIT_CREDTYPE_SSH_MEMORY = (1u << 6), -} git_credtype_t; - -typedef struct git_cred git_cred; - -/** - * The base structure for all credential types - */ -struct git_cred { - git_credtype_t credtype; /**< A type of credential */ - void GIT_CALLBACK(free)(git_cred *cred); -}; - -/** A plaintext username and password */ -typedef struct { - git_cred parent; - char *username; - char *password; -} git_cred_userpass_plaintext; - - -/* - * If the user hasn't included libssh2.h before git2.h, we need to - * define a few types for the callback signatures. - */ -#ifndef LIBSSH2_VERSION -typedef struct _LIBSSH2_SESSION LIBSSH2_SESSION; -typedef struct _LIBSSH2_USERAUTH_KBDINT_PROMPT LIBSSH2_USERAUTH_KBDINT_PROMPT; -typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE LIBSSH2_USERAUTH_KBDINT_RESPONSE; -#endif - -typedef int GIT_CALLBACK(git_cred_sign_callback)(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, const unsigned char *data, size_t data_len, void **abstract); -typedef void GIT_CALLBACK(git_cred_ssh_interactive_callback)(const char* name, int name_len, const char* instruction, int instruction_len, int num_prompts, const LIBSSH2_USERAUTH_KBDINT_PROMPT* prompts, LIBSSH2_USERAUTH_KBDINT_RESPONSE* responses, void **abstract); - -/** - * A ssh key from disk - */ -typedef struct git_cred_ssh_key { - git_cred parent; - char *username; - char *publickey; - char *privatekey; - char *passphrase; -} git_cred_ssh_key; - -/** - * Keyboard-interactive based ssh authentication - */ -typedef struct git_cred_ssh_interactive { - git_cred parent; - char *username; - git_cred_ssh_interactive_callback prompt_callback; - void *payload; -} git_cred_ssh_interactive; - -/** - * A key with a custom signature function - */ -typedef struct git_cred_ssh_custom { - git_cred parent; - char *username; - char *publickey; - size_t publickey_len; - git_cred_sign_callback sign_callback; - void *payload; -} git_cred_ssh_custom; - -/** A key for NTLM/Kerberos "default" credentials */ -typedef struct git_cred git_cred_default; - -/** Username-only credential information */ -typedef struct git_cred_username { - git_cred parent; - char username[1]; -} git_cred_username; - -/** - * Check whether a credential object contains username information. - * - * @param cred object to check - * @return 1 if the credential object has non-NULL username, 0 otherwise - */ -GIT_EXTERN(int) git_cred_has_username(git_cred *cred); - -/** - * Create a new plain-text username and password credential object. - * The supplied credential parameter will be internally duplicated. - * - * @param out The newly created credential object. - * @param username The username of the credential. - * @param password The password of the credential. - * @return 0 for success or an error code for failure - */ -GIT_EXTERN(int) git_cred_userpass_plaintext_new( - git_cred **out, - const char *username, - const char *password); - -/** - * Create a new passphrase-protected ssh key credential object. - * The supplied credential parameter will be internally duplicated. - * - * @param out The newly created credential object. - * @param username username to use to authenticate - * @param publickey The path to the public key of the credential. - * @param privatekey The path to the private key of the credential. - * @param passphrase The passphrase of the credential. - * @return 0 for success or an error code for failure - */ -GIT_EXTERN(int) git_cred_ssh_key_new( - git_cred **out, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase); - -/** - * Create a new ssh keyboard-interactive based credential object. - * The supplied credential parameter will be internally duplicated. - * - * @param username Username to use to authenticate. - * @param prompt_callback The callback method used for prompts. - * @param payload Additional data to pass to the callback. - * @return 0 for success or an error code for failure. - */ -GIT_EXTERN(int) git_cred_ssh_interactive_new( - git_cred **out, - const char *username, - git_cred_ssh_interactive_callback prompt_callback, - void *payload); - -/** - * Create a new ssh key credential object used for querying an ssh-agent. - * The supplied credential parameter will be internally duplicated. - * - * @param out The newly created credential object. - * @param username username to use to authenticate - * @return 0 for success or an error code for failure - */ -GIT_EXTERN(int) git_cred_ssh_key_from_agent( - git_cred **out, - const char *username); - -/** - * Create an ssh key credential with a custom signing function. - * - * This lets you use your own function to sign the challenge. - * - * This function and its credential type is provided for completeness - * and wraps `libssh2_userauth_publickey()`, which is undocumented. - * - * The supplied credential parameter will be internally duplicated. - * - * @param out The newly created credential object. - * @param username username to use to authenticate - * @param publickey The bytes of the public key. - * @param publickey_len The length of the public key in bytes. - * @param sign_callback The callback method to sign the data during the challenge. - * @param payload Additional data to pass to the callback. - * @return 0 for success or an error code for failure - */ -GIT_EXTERN(int) git_cred_ssh_custom_new( - git_cred **out, - const char *username, - const char *publickey, - size_t publickey_len, - git_cred_sign_callback sign_callback, - void *payload); - -/** - * Create a "default" credential usable for Negotiate mechanisms like NTLM - * or Kerberos authentication. - * - * @return 0 for success or an error code for failure - */ -GIT_EXTERN(int) git_cred_default_new(git_cred **out); - -/** - * Create a credential to specify a username. - * - * This is used with ssh authentication to query for the username if - * none is specified in the url. - */ -GIT_EXTERN(int) git_cred_username_new(git_cred **cred, const char *username); - -/** - * Create a new ssh key credential object reading the keys from memory. - * - * @param out The newly created credential object. - * @param username username to use to authenticate. - * @param publickey The public key of the credential. - * @param privatekey The private key of the credential. - * @param passphrase The passphrase of the credential. - * @return 0 for success or an error code for failure - */ -GIT_EXTERN(int) git_cred_ssh_key_memory_new( - git_cred **out, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase); - - -/** - * Free a credential. - * - * This is only necessary if you own the object; that is, if you are a - * transport. - * - * @param cred the object to free - */ -GIT_EXTERN(void) git_cred_free(git_cred *cred); - -/** - * Signature of a function which acquires a credential object. - * - * @param cred The newly created credential object. - * @param url The resource for which we are demanding a credential. - * @param username_from_url The username that was embedded in a "user\@host" - * remote url, or NULL if not included. - * @param allowed_types A bitmask stating which cred types are OK to return. - * @param payload The payload provided when specifying this callback. - * @return 0 for success, < 0 to indicate an error, > 0 to indicate - * no credential was acquired - */ -typedef int GIT_CALLBACK(git_cred_acquire_cb)( - git_cred **cred, - const char *url, - const char *username_from_url, - unsigned int allowed_types, - void *payload); - /** @} */ GIT_END_DECL + #endif diff --git a/include/git2/tree.h b/include/git2/tree.h index aca5d75ed..1a8e155fc 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -258,8 +258,9 @@ GIT_EXTERN(int) git_treebuilder_new( * Clear all the entires in the builder * * @param bld Builder to clear + * @return 0 on success; error code otherwise */ -GIT_EXTERN(void) git_treebuilder_clear(git_treebuilder *bld); +GIT_EXTERN(int) git_treebuilder_clear(git_treebuilder *bld); /** * Get the number of entries listed in a treebuilder @@ -267,7 +268,7 @@ GIT_EXTERN(void) git_treebuilder_clear(git_treebuilder *bld); * @param bld a previously loaded treebuilder. * @return the number of entries in the treebuilder */ -GIT_EXTERN(unsigned int) git_treebuilder_entrycount(git_treebuilder *bld); +GIT_EXTERN(size_t) git_treebuilder_entrycount(git_treebuilder *bld); /** * Free a tree builder @@ -357,8 +358,9 @@ typedef int GIT_CALLBACK(git_treebuilder_filter_cb)( * @param bld Tree builder * @param filter Callback to filter entries * @param payload Extra data to pass to filter callback + * @return 0 on success, non-zero callback return value, or error code */ -GIT_EXTERN(void) git_treebuilder_filter( +GIT_EXTERN(int) git_treebuilder_filter( git_treebuilder *bld, git_treebuilder_filter_cb filter, void *payload); diff --git a/include/git2/types.h b/include/git2/types.h index 0eaa0041d..ade0c7d32 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -63,6 +63,9 @@ typedef int64_t git_time_t; /**< time in seconds from epoch */ #endif +/** The maximum size of an object */ +typedef uint64_t git_object_size_t; + #include "buffer.h" #include "oid.h" @@ -244,98 +247,10 @@ typedef struct git_push git_push; typedef struct git_remote_head git_remote_head; typedef struct git_remote_callbacks git_remote_callbacks; -/** - * This is passed as the first argument to the callback to allow the - * user to see the progress. - * - * - total_objects: number of objects in the packfile being downloaded - * - indexed_objects: received objects that have been hashed - * - received_objects: objects which have been downloaded - * - local_objects: locally-available objects that have been injected - * in order to fix a thin pack. - * - received-bytes: size of the packfile received up to now - */ -typedef struct git_transfer_progress { - unsigned int total_objects; - unsigned int indexed_objects; - unsigned int received_objects; - unsigned int local_objects; - unsigned int total_deltas; - unsigned int indexed_deltas; - size_t received_bytes; -} git_transfer_progress; - -/** - * Type for progress callbacks during indexing. Return a value less than zero - * to cancel the transfer. - * - * @param stats Structure containing information about the state of the transfer - * @param payload Payload provided by caller - */ -typedef int GIT_CALLBACK(git_transfer_progress_cb)(const git_transfer_progress *stats, void *payload); - -/** - * Type for messages delivered by the transport. Return a negative value - * to cancel the network operation. - * - * @param str The message from the transport - * @param len The length of the message - * @param payload Payload provided by the caller - */ -typedef int GIT_CALLBACK(git_transport_message_cb)(const char *str, int len, void *payload); - - -/** - * Type of host certificate structure that is passed to the check callback - */ -typedef enum git_cert_t { - /** - * No information about the certificate is available. This may - * happen when using curl. - */ - GIT_CERT_NONE, - /** - * The `data` argument to the callback will be a pointer to - * the DER-encoded data. - */ - GIT_CERT_X509, - /** - * The `data` argument to the callback will be a pointer to a - * `git_cert_hostkey` structure. - */ - GIT_CERT_HOSTKEY_LIBSSH2, - /** - * The `data` argument to the callback will be a pointer to a - * `git_strarray` with `name:content` strings containing - * information about the certificate. This is used when using - * curl. - */ - GIT_CERT_STRARRAY, -} git_cert_t; - /** * Parent type for `git_cert_hostkey` and `git_cert_x509`. */ -typedef struct { - /** - * Type of certificate. A `GIT_CERT_` value. - */ - git_cert_t cert_type; -} git_cert; - -/** - * Callback for the user's custom certificate checks. - * - * @param cert The host certificate - * @param valid Whether the libgit2 checks (OpenSSL or WinHTTP) think - * this certificate is valid - * @param host Hostname of the host libgit2 connected to - * @param payload Payload provided by the caller - * @return 0 to proceed with the connection, < 0 to fail the connection - * or > 0 to indicate that the callback refused to act and that - * the existing validity determination should be honored - */ -typedef int GIT_CALLBACK(git_transport_certificate_check_cb)(git_cert *cert, int valid, const char *host, void *payload); +typedef struct git_cert git_cert; /** * Opaque structure representing a submodule. diff --git a/include/git2/version.h b/include/git2/version.h index 3131d90d5..2d8708622 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -7,12 +7,12 @@ #ifndef INCLUDE_git_version_h__ #define INCLUDE_git_version_h__ -#define LIBGIT2_VERSION "0.28.4" -#define LIBGIT2_VER_MAJOR 0 -#define LIBGIT2_VER_MINOR 28 -#define LIBGIT2_VER_REVISION 4 +#define LIBGIT2_VERSION "1.0.1" +#define LIBGIT2_VER_MAJOR 1 +#define LIBGIT2_VER_MINOR 0 +#define LIBGIT2_VER_REVISION 1 #define LIBGIT2_VER_PATCH 0 -#define LIBGIT2_SOVERSION 28 +#define LIBGIT2_SOVERSION "1.0" #endif diff --git a/include/git2/worktree.h b/include/git2/worktree.h index 925d85a69..049511da1 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -78,7 +78,7 @@ GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt); * Worktree add options structure * * Initialize with `GIT_WORKTREE_ADD_OPTIONS_INIT`. Alternatively, you can - * use `git_worktree_add_init_options`. + * use `git_worktree_add_options_init`. * */ typedef struct git_worktree_add_options { @@ -101,7 +101,7 @@ typedef struct git_worktree_add_options { * @param version The struct version; pass `GIT_WORKTREE_ADD_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_worktree_add_init_options(git_worktree_add_options *opts, +GIT_EXTERN(int) git_worktree_add_options_init(git_worktree_add_options *opts, unsigned int version); /** @@ -174,7 +174,7 @@ GIT_EXTERN(const char *) git_worktree_name(const git_worktree *wt); * is valid for the lifetime of the git_worktree. */ GIT_EXTERN(const char *) git_worktree_path(const git_worktree *wt); - + /** * Flags which can be passed to git_worktree_prune to alter its * behavior. @@ -192,7 +192,7 @@ typedef enum { * Worktree prune options structure * * Initialize with `GIT_WORKTREE_PRUNE_OPTIONS_INIT`. Alternatively, you can - * use `git_worktree_prune_init_options`. + * use `git_worktree_prune_options_init`. * */ typedef struct git_worktree_prune_options { @@ -214,7 +214,7 @@ typedef struct git_worktree_prune_options { * @param version The struct version; pass `GIT_WORKTREE_PRUNE_OPTIONS_VERSION`. * @return Zero on success; -1 on failure. */ -GIT_EXTERN(int) git_worktree_prune_init_options( +GIT_EXTERN(int) git_worktree_prune_options_init( git_worktree_prune_options *opts, unsigned int version); diff --git a/libgit2.pc.in b/libgit2.pc.in deleted file mode 100644 index 96b965955..000000000 --- a/libgit2.pc.in +++ /dev/null @@ -1,13 +0,0 @@ -prefix="@PKGCONFIG_PREFIX@" -libdir=@PKGCONFIG_LIBDIR@ -includedir=@PKGCONFIG_INCLUDEDIR@ - -Name: libgit2 -Description: The git library, take 2 -Version: @LIBGIT2_VERSION_STRING@ - -Libs: -L${libdir} -lgit2 -Libs.private: @LIBGIT2_PC_LIBS@ -Requires.private: @LIBGIT2_PC_REQUIRES@ - -Cflags: -I${includedir} diff --git a/package.json b/package.json index d33f31c30..79cddbee7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libgit2", - "version": "0.27.0", + "version": "1.0.1", "repo": "https://github.com/libgit2/libgit2", "description": " A cross-platform, linkable library implementation of Git that you can use in your application.", "install": "mkdir build && cd build && cmake .. && cmake --build ." diff --git a/script/leaks.sh b/script/leaks.sh new file mode 100755 index 000000000..efeead516 --- /dev/null +++ b/script/leaks.sh @@ -0,0 +1,6 @@ +#!/bin/sh +export MallocStackLogging=1 +export MallocScribble=1 +export MallocLogFile=/dev/null +export CLAR_AT_EXIT="leaks -quiet \$PPID" +exec "$@" diff --git a/script/release.py b/script/release.py new file mode 100755 index 000000000..3d8e9b806 --- /dev/null +++ b/script/release.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python + +from collections import namedtuple + +import argparse +import base64 +import copy +import json +import subprocess +import sys +import urllib.parse +import urllib.request +import urllib.error + +class Error(Exception): + pass + +class Version(object): + def __init__(self, version): + versions = version.split(sep='.') + if len(versions) < 2 or len(versions) > 3: + raise Error("Invalid version string '{}'".format(version)) + self.major = int(versions[0]) + self.minor = int(versions[1]) + self.revision = int(versions[2]) if len(versions) == 3 else 0 + + def __str__(self): + return '{}.{}.{}'.format(self.major, self.minor, self.revision) + + def __eq__(self, other): + return self.major == other.major and self.minor == other.minor and self.revision == other.revision + +def verify_version(version): + expected = { + 'VERSION': [ '"{}"'.format(version), None ], + 'VER_MAJOR': [ str(version.major), None ], + 'VER_MINOR': [ str(version.minor), None ], + 'VER_REVISION': [ str(version.revision), None ], + 'VER_PATCH': [ '0', None ], + 'SOVERSION': [ '"{}.{}"'.format(version.major, version.minor), None ], + } + + with open('include/git2/version.h') as f: + lines = f.readlines() + + for key in expected.keys(): + define = '#define LIBGIT2_{} '.format(key) + for line in lines: + if line.startswith(define): + expected[key][1] = line[len(define):].strip() + break + else: + raise Error("version.h: missing define for '{}'".format(key)) + + for k, v in expected.items(): + if v[0] != v[1]: + raise Error("version.h: define '{}' does not match (got '{}', expected '{}')".format(k, v[0], v[1])) + + with open('package.json') as f: + pkg = json.load(f) + + try: + pkg_version = Version(pkg["version"]) + except KeyError as err: + raise Error("package.json: missing the field {}".format(err)) + + if pkg_version != version: + raise Error("package.json: version does not match (got '{}', expected '{}')".format(pkg_version, version)) + +def generate_relnotes(tree, version): + with open('docs/changelog.md') as f: + lines = f.readlines() + + if not lines[0].startswith('v'): + raise Error("changelog.md: missing section for v{}".format(version)) + try: + v = Version(lines[0][1:].strip()) + except: + raise Error("changelog.md: invalid version string {}".format(lines[0].strip())) + if v != version: + raise Error("changelog.md: changelog version doesn't match (got {}, expected {})".format(v, version)) + if not lines[1].startswith('----'): + raise Error("changelog.md: missing version header") + if lines[2] != '\n': + raise Error("changelog.md: missing newline after version header") + + for i, line in enumerate(lines[3:]): + if not line.startswith('v'): + continue + try: + Version(line[1:].strip()) + break + except: + continue + else: + raise Error("changelog.md: cannot find section header of preceding release") + + return ''.join(lines[3:i + 3]).strip() + +def git(*args): + process = subprocess.run([ 'git', *args ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if process.returncode != 0: + raise Error('Failed executing git {}: {}'.format(' '.join(args), process.stderr.decode())) + return process.stdout + +def post(url, data, contenttype, user, password): + request = urllib.request.Request(url, data=data) + request.add_header('Accept', 'application/json') + request.add_header('Content-Type', contenttype) + request.add_header('Content-Length', len(data)) + request.add_header('Authorization', 'Basic ' + base64.b64encode('{}:{}'.format(user, password).encode()).decode()) + + try: + response = urllib.request.urlopen(request) + if response.getcode() != 201: + raise Error("POST to '{}' failed: {}".format(url, response.reason)) + except urllib.error.URLError as e: + raise Error("POST to '{}' failed: {}".format(url, e)) + data = json.load(response) + + return data + +def generate_asset(version, tree, archive_format): + Asset = namedtuple('Asset', ['name', 'label', 'mimetype', 'data']) + mimetype = 'application/{}'.format('gzip' if archive_format == 'tar.gz' else 'zip') + return Asset( + "libgit2-{}.{}".format(version, archive_format), "Release sources: libgit2-{}.{}".format(version, archive_format), mimetype, + git('archive', '--format', archive_format, '--prefix', 'libgit2-{}/'.format(version), tree) + ) + +def release(args): + params = { + "tag_name": 'v' + str(args.version), + "name": 'libgit2 v' + str(args.version), + "target_commitish": git('rev-parse', args.tree).decode().strip(), + "body": generate_relnotes(args.tree, args.version), + } + assets = [ + generate_asset(args.version, args.tree, 'tar.gz'), + generate_asset(args.version, args.tree, 'zip'), + ] + + if args.dryrun: + for k, v in params.items(): + print('{}: {}'.format(k, v)) + for asset in assets: + print('asset: name={}, label={}, mimetype={}, bytes={}'.format(asset.name, asset.label, asset.mimetype, len(asset.data))) + return + + try: + url = 'https://api.github.com/repos/{}/releases'.format(args.repository) + response = post(url, json.dumps(params).encode(), 'application/json', args.user, args.password) + except Error as e: + raise Error('Could not create release: ' + str(e)) + + for asset in assets: + try: + url = list(urllib.parse.urlparse(response['upload_url'].split('{?')[0])) + url[4] = urllib.parse.urlencode({ 'name': asset.name, 'label': asset.label }) + post(urllib.parse.urlunparse(url), asset.data, asset.mimetype, args.user, args.password) + except Error as e: + raise Error('Could not upload asset: ' + str(e)) + +def main(): + parser = argparse.ArgumentParser(description='Create a libgit2 release') + parser.add_argument('--tree', default='HEAD', help='tree to create release for (default: HEAD)') + parser.add_argument('--dryrun', action='store_true', help='generate release, but do not post it') + parser.add_argument('--repository', default='libgit2/libgit2', help='GitHub repository to create repository in') + parser.add_argument('--user', help='user to authenitcate as') + parser.add_argument('--password', help='password to authenticate with') + parser.add_argument('version', type=Version, help='version of the new release') + args = parser.parse_args() + + verify_version(args.version) + release(args) + +if __name__ == '__main__': + try: + main() + except Error as e: + print(e) + sys.exit(1) diff --git a/script/valgrind.sh b/script/valgrind.sh new file mode 100755 index 000000000..b5deed2b0 --- /dev/null +++ b/script/valgrind.sh @@ -0,0 +1,2 @@ +#!/bin/bash +exec valgrind --leak-check=full --show-reachable=yes --error-exitcode=125 --num-callers=50 --suppressions="$(dirname "${BASH_SOURCE[0]}")/valgrind.supp" "$@" diff --git a/libgit2_clar.supp b/script/valgrind.supp similarity index 63% rename from libgit2_clar.supp rename to script/valgrind.supp index b74791974..d938aa9c9 100644 --- a/libgit2_clar.supp +++ b/script/valgrind.supp @@ -55,6 +55,15 @@ fun:curl_global_init } +{ + ignore-libssh2-init + Memcheck:Leak + ... + fun:gcry_control + fun:libssh2_init + ... +} + { ignore-libssh2-gcrypt-control-leak Memcheck:Leak @@ -77,6 +86,7 @@ ... fun:gcry_mpi_scan obj:*libssh2.so* + ... } { @@ -103,6 +113,54 @@ obj:*libssh2.so* } +{ + ignore-libssh2-gcrypt-session-handshake + Memcheck:Leak + ... + obj:*libssh2.so* + obj:*libssh2.so* + fun:libssh2_session_handshake + ... +} + +{ + ignore-openssl-undefined-in-read + Memcheck:Cond + ... + obj:*libssl.so* + ... + fun:openssl_read + ... +} + +{ + ignore-openssl-undefined-in-connect + Memcheck:Cond + ... + obj:*libssl.so* + ... + fun:openssl_connect + ... +} + +{ + ignore-libssh2-rsa-sha1-sign + Memcheck:Leak + ... + obj:*libgcrypt.so* + fun:_libssh2_rsa_sha1_sign + ... +} + +{ + ignore-libssh2-kexinit + Memcheck:Leak + ... + obj:*libssh2.so* + fun:kexinit + ... +} + { ignore-noai6ai_cached-double-free Memcheck:Free @@ -112,3 +170,11 @@ fun:exit ... } + +{ + ignore-libcrypto-uninitialized-read-for-entropy + Memcheck:Value8 + ... + obj:*libcrypto.so* + ... +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 213dd795c..dff1d94e8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,8 @@ IF(DEBUG_POOL) ENDIF() ADD_FEATURE_INFO(debugpool GIT_DEBUG_POOL "debug pool allocator") +INCLUDE(PkgBuildConfig) + # This variable will contain the libraries we need to put into # libgit2.pc's Requires.private. That is, what we're linking to or # what someone who's statically linking us needs to link to. @@ -19,52 +21,12 @@ SET(LIBGIT2_INCLUDES SET(LIBGIT2_SYSTEM_INCLUDES "") SET(LIBGIT2_LIBS "") -# Installation paths -# -SET(BIN_INSTALL_DIR bin CACHE PATH "Where to install binaries to.") -SET(LIB_INSTALL_DIR lib CACHE PATH "Where to install libraries to.") -SET(INCLUDE_INSTALL_DIR include CACHE PATH "Where to install headers to.") - -# Set a couple variables to be substituted inside the .pc file. -# We can't just use LIB_INSTALL_DIR in the .pc file, as passing them as absolue -# or relative paths is both valid and supported by cmake. -SET (PKGCONFIG_PREFIX ${CMAKE_INSTALL_PREFIX}) - -IF(IS_ABSOLUTE ${LIB_INSTALL_DIR}) - SET (PKGCONFIG_LIBDIR ${LIB_INSTALL_DIR}) -ELSE(IS_ABSOLUTE ${LIB_INSTALL_DIR}) - SET (PKGCONFIG_LIBDIR "\${prefix}/${LIB_INSTALL_DIR}") -ENDIF (IS_ABSOLUTE ${LIB_INSTALL_DIR}) - -IF(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) - SET (PKGCONFIG_INCLUDEDIR ${INCLUDE_INSTALL_DIR}) -ELSE(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) - SET (PKGCONFIG_INCLUDEDIR "\${prefix}/${INCLUDE_INSTALL_DIR}") -ENDIF(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) - # Enable tracing -IF (ENABLE_TRACE STREQUAL "ON") +IF(ENABLE_TRACE) SET(GIT_TRACE 1) ENDIF() ADD_FEATURE_INFO(tracing GIT_TRACE "tracing support") -# Use `regcomp_l` if available -CHECK_SYMBOL_EXISTS(regcomp_l "regex.h;xlocale.h" HAVE_REGCOMP_L) -IF (HAVE_REGCOMP_L) - SET(GIT_USE_REGCOMP_L 1) -ENDIF () - -# Otherwise, we either want to use system's `regcomp` or our -# bundled regcomp code, if system doesn't provide `regcomp`. -IF(NOT HAVE_REGCOMP_L) - CHECK_FUNCTION_EXISTS(regcomp HAVE_REGCOMP) - IF(NOT HAVE_REGCOMP) - ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/regex" "${libgit2_BINARY_DIR}/deps/regex") - LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/regex") - LIST(APPEND LIBGIT2_OBJECTS $) - ENDIF() -ENDIF() - CHECK_FUNCTION_EXISTS(futimens HAVE_FUTIMENS) IF (HAVE_FUTIMENS) SET(GIT_USE_FUTIMENS 1) @@ -139,180 +101,73 @@ IF (WIN32 AND WINHTTP) LIST(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32") ENDIF() -IF (USE_HTTPS) - # We try to find any packages our backends might use - FIND_PACKAGE(OpenSSL) - FIND_PACKAGE(mbedTLS) - IF (CMAKE_SYSTEM_NAME MATCHES "Darwin") - FIND_PACKAGE(Security) - FIND_PACKAGE(CoreFoundation) - ENDIF() +Include(SelectHTTPSBackend) +Include(SelectHashes) - # Auto-select TLS backend - IF (USE_HTTPS STREQUAL ON) - IF (SECURITY_FOUND) - IF (SECURITY_HAS_SSLCREATECONTEXT) - SET(HTTPS_BACKEND "SecureTransport") - ELSE() - MESSAGE("-- Security framework is too old, falling back to OpenSSL") - SET(HTTPS_BACKEND "OpenSSL") - ENDIF() - ELSEIF (WINHTTP) - SET(HTTPS_BACKEND "WinHTTP") - ELSEIF(OPENSSL_FOUND) - SET(HTTPS_BACKEND "OpenSSL") - ELSEIF(MBEDTLS_FOUND) - SET(HTTPS_BACKEND "mbedTLS") - ELSE() - MESSAGE(FATAL_ERROR "Unable to autodetect a usable HTTPS backend." - "Please pass the backend name explicitly (-DUSE_HTTPS=backend)") - ENDIF() +# Specify regular expression implementation +FIND_PACKAGE(PCRE) + +IF(REGEX_BACKEND STREQUAL "") + CHECK_SYMBOL_EXISTS(regcomp_l "regex.h;xlocale.h" HAVE_REGCOMP_L) + + IF(HAVE_REGCOMP_L) + SET(REGEX_BACKEND "regcomp_l") + ELSEIF(PCRE_FOUND) + SET(REGEX_BACKEND "pcre") ELSE() - # Backend was explicitly set - SET(HTTPS_BACKEND ${USE_HTTPS}) + SET(REGEX_BACKEND "builtin") ENDIF() - - # Check that we can find what's required for the selected backend - IF (HTTPS_BACKEND STREQUAL "SecureTransport") - IF (NOT COREFOUNDATION_FOUND) - MESSAGE(FATAL_ERROR "Cannot use SecureTransport backend, CoreFoundation.framework not found") - ENDIF() - IF (NOT SECURITY_FOUND) - MESSAGE(FATAL_ERROR "Cannot use SecureTransport backend, Security.framework not found") - ENDIF() - IF (NOT SECURITY_HAS_SSLCREATECONTEXT) - MESSAGE(FATAL_ERROR "Cannot use SecureTransport backend, SSLCreateContext not supported") - ENDIF() - - SET(GIT_SECURE_TRANSPORT 1) - LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${SECURITY_INCLUDE_DIR}) - LIST(APPEND LIBGIT2_LIBS ${COREFOUNDATION_LIBRARIES} ${SECURITY_LIBRARIES}) - LIST(APPEND LIBGIT2_PC_LIBS ${COREFOUNDATION_LDFLAGS} ${SECURITY_LDFLAGS}) - ELSEIF (HTTPS_BACKEND STREQUAL "OpenSSL") - IF (NOT OPENSSL_FOUND) - MESSAGE(FATAL_ERROR "Asked for OpenSSL TLS backend, but it wasn't found") - ENDIF() - - SET(GIT_OPENSSL 1) - LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${OPENSSL_INCLUDE_DIR}) - LIST(APPEND LIBGIT2_LIBS ${OPENSSL_LIBRARIES}) - LIST(APPEND LIBGIT2_PC_LIBS ${OPENSSL_LDFLAGS}) - LIST(APPEND LIBGIT2_PC_REQUIRES "openssl") - ELSEIF(HTTPS_BACKEND STREQUAL "mbedTLS") - IF (NOT MBEDTLS_FOUND) - MESSAGE(FATAL_ERROR "Asked for mbedTLS backend, but it wasn't found") - ENDIF() - - IF(NOT CERT_LOCATION) - MESSAGE("Auto-detecting default certificates location") - IF(CMAKE_SYSTEM_NAME MATCHES Darwin) - # Check for an Homebrew installation - SET(OPENSSL_CMD "/usr/local/opt/openssl/bin/openssl") - ELSE() - SET(OPENSSL_CMD "openssl") - ENDIF() - EXECUTE_PROCESS(COMMAND ${OPENSSL_CMD} version -d OUTPUT_VARIABLE OPENSSL_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) - IF(OPENSSL_DIR) - STRING(REGEX REPLACE "^OPENSSLDIR: \"(.*)\"$" "\\1/" OPENSSL_DIR ${OPENSSL_DIR}) - - SET(OPENSSL_CA_LOCATIONS - "ca-bundle.pem" # OpenSUSE Leap 42.1 - "cert.pem" # Ubuntu 14.04, FreeBSD - "certs/ca-certificates.crt" # Ubuntu 16.04 - "certs/ca.pem" # Debian 7 - ) - FOREACH(SUFFIX IN LISTS OPENSSL_CA_LOCATIONS) - SET(LOC "${OPENSSL_DIR}${SUFFIX}") - IF(NOT CERT_LOCATION AND EXISTS "${OPENSSL_DIR}${SUFFIX}") - SET(CERT_LOCATION ${LOC}) - ENDIF() - ENDFOREACH() - ELSE() - MESSAGE("Unable to find OpenSSL executable. Please provide default certificate location via CERT_LOCATION") - ENDIF() - ENDIF() - - IF(CERT_LOCATION) - IF(NOT EXISTS ${CERT_LOCATION}) - MESSAGE(FATAL_ERROR "Cannot use CERT_LOCATION=${CERT_LOCATION} as it doesn't exist") - ENDIF() - ADD_FEATURE_INFO(CERT_LOCATION ON "using certificates from ${CERT_LOCATION}") - ADD_DEFINITIONS(-DGIT_DEFAULT_CERT_LOCATION="${CERT_LOCATION}") - ENDIF() - - SET(GIT_MBEDTLS 1) - LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${MBEDTLS_INCLUDE_DIR}) - LIST(APPEND LIBGIT2_LIBS ${MBEDTLS_LIBRARIES}) - # mbedTLS has no pkgconfig file, hence we can't require it - # https://github.com/ARMmbed/mbedtls/issues/228 - # For now, pass its link flags as our own - LIST(APPEND LIBGIT2_PC_LIBS ${MBEDTLS_LIBRARIES}) - ELSEIF (HTTPS_BACKEND STREQUAL "WinHTTP") - # WinHTTP setup was handled in the WinHTTP-specific block above - ELSE() - MESSAGE(FATAL_ERROR "Asked for backend ${HTTPS_BACKEND} but it wasn't found") - ENDIF() - - ADD_FEATURE_INFO(HTTPS ON "using ${HTTPS_BACKEND}") - SET(GIT_HTTPS 1) -ELSE() - ADD_FEATURE_INFO(HTTPS OFF "no support") ENDIF() -# Specify sha1 implementation -IF(SHA1_BACKEND STREQUAL "OpenSSL") - IF(NOT OPENSSL_FOUND) - FIND_PACKAGE(OpenSSL) - IF(NOT OPENSSL_FOUND) - MESSAGE(FATAL_ERROR "Requested OpenSSL SHA1 backend, but OpenSSL could not be found") - ENDIF() +IF(REGEX_BACKEND STREQUAL "regcomp_l") + ADD_FEATURE_INFO(regex ON "using system regcomp_l") + SET(GIT_REGEX_REGCOMP_L 1) +ELSEIF(REGEX_BACKEND STREQUAL "pcre2") + FIND_PACKAGE(PCRE2) + + IF(NOT PCRE2_FOUND) + MESSAGE(FATAL_ERROR "PCRE2 support was requested but not found") ENDIF() - ADD_FEATURE_INFO(SHA ON "using OpenSSL") - SET(GIT_SHA1_OPENSSL 1) - IF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") - LIST(APPEND LIBGIT2_PC_LIBS "-lssl") - ELSE() - LIST(APPEND LIBGIT2_PC_REQUIRES "openssl") - ENDIF() -ELSEIF(SHA1_BACKEND STREQUAL "CollisionDetection") - ADD_FEATURE_INFO(SHA ON "using CollisionDetection") - SET(GIT_SHA1_COLLISIONDETECT 1) - ADD_DEFINITIONS(-DSHA1DC_NO_STANDARD_INCLUDES=1) - ADD_DEFINITIONS(-DSHA1DC_CUSTOM_INCLUDE_SHA1_C=\"common.h\") - ADD_DEFINITIONS(-DSHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C=\"common.h\") - FILE(GLOB SRC_SHA1 hash/hash_collisiondetect.c hash/sha1dc/*) -ELSEIF(SHA1_BACKEND STREQUAL "Generic") - ADD_FEATURE_INFO(SHA ON "using Generic") - FILE(GLOB SRC_SHA1 hash/hash_generic.c) -ELSEIF(SHA1_BACKEND STREQUAL "Win32") - ADD_FEATURE_INFO(SHA ON "using Win32") - SET(GIT_SHA1_WIN32 1) - FILE(GLOB SRC_SHA1 hash/hash_win32.c) -ELSEIF(SHA1_BACKEND STREQUAL "CommonCrypto") - ADD_FEATURE_INFO(SHA ON "using CommonCrypto") - SET(GIT_SHA1_COMMON_CRYPTO 1) -ELSEIF (SHA1_BACKEND STREQUAL "mbedTLS") - ADD_FEATURE_INFO(SHA ON "using mbedTLS") - SET(GIT_SHA1_MBEDTLS 1) - FILE(GLOB SRC_SHA1 hash/hash_mbedtls.c) - LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${MBEDTLS_INCLUDE_DIR}) - LIST(APPEND LIBGIT2_LIBS ${MBEDTLS_LIBRARIES}) - # mbedTLS has no pkgconfig file, hence we can't require it - # https://github.com/ARMmbed/mbedtls/issues/228 - # For now, pass its link flags as our own - LIST(APPEND LIBGIT2_PC_LIBS ${MBEDTLS_LIBRARIES}) + ADD_FEATURE_INFO(regex ON "using system PCRE2") + SET(GIT_REGEX_PCRE2 1) + + LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${PCRE2_INCLUDE_DIRS}) + LIST(APPEND LIBGIT2_LIBS ${PCRE2_LIBRARIES}) + LIST(APPEND LIBGIT2_PC_REQUIRES "libpcre2-8") +ELSEIF(REGEX_BACKEND STREQUAL "pcre") + ADD_FEATURE_INFO(regex ON "using system PCRE") + SET(GIT_REGEX_PCRE 1) + + LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${PCRE_INCLUDE_DIRS}) + LIST(APPEND LIBGIT2_LIBS ${PCRE_LIBRARIES}) + LIST(APPEND LIBGIT2_PC_REQUIRES "libpcre") +ELSEIF(REGEX_BACKEND STREQUAL "regcomp") + ADD_FEATURE_INFO(regex ON "using system regcomp") + SET(GIT_REGEX_REGCOMP 1) +ELSEIF(REGEX_BACKEND STREQUAL "builtin") + ADD_FEATURE_INFO(regex ON "using bundled PCRE") + SET(GIT_REGEX_BUILTIN 1) + + ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/pcre" "${libgit2_BINARY_DIR}/deps/pcre") + LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/pcre") + LIST(APPEND LIBGIT2_OBJECTS $) ELSE() - MESSAGE(FATAL_ERROR "Asked for unknown SHA1 backend ${SHA1_BACKEND}") + MESSAGE(FATAL_ERROR "The REGEX_BACKEND option provided is not supported") ENDIF() # Optional external dependency: http-parser -FIND_PACKAGE(HTTP_Parser) -IF (USE_EXT_HTTP_PARSER AND HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2) - LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${HTTP_PARSER_INCLUDE_DIRS}) - LIST(APPEND LIBGIT2_LIBS ${HTTP_PARSER_LIBRARIES}) - LIST(APPEND LIBGIT2_PC_LIBS "-lhttp_parser") - ADD_FEATURE_INFO(http-parser ON "http-parser support") +IF(USE_HTTP_PARSER STREQUAL "system") + FIND_PACKAGE(HTTP_Parser) + + IF (HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2) + LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${HTTP_PARSER_INCLUDE_DIRS}) + LIST(APPEND LIBGIT2_LIBS ${HTTP_PARSER_LIBRARIES}) + LIST(APPEND LIBGIT2_PC_LIBS "-lhttp_parser") + ADD_FEATURE_INFO(http-parser ON "http-parser support (system)") + ELSE() + MESSAGE(FATAL_ERROR "http-parser support was requested but not found") + ENDIF() ELSE() MESSAGE(STATUS "http-parser version 2 was not found or disabled; using bundled 3rd-party sources.") ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/http-parser" "${libgit2_BINARY_DIR}/deps/http-parser") @@ -328,7 +183,6 @@ IF(NOT USE_BUNDLED_ZLIB) LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${ZLIB_INCLUDE_DIRS}) LIST(APPEND LIBGIT2_LIBS ${ZLIB_LIBRARIES}) IF(APPLE OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD") - LIST(APPEND LIBGIT2_LIBS "z") LIST(APPEND LIBGIT2_PC_LIBS "-lz") ELSE() LIST(APPEND LIBGIT2_PC_REQUIRES "zlib") @@ -364,15 +218,18 @@ ELSE() ENDIF() ADD_FEATURE_INFO(SSH GIT_SSH "SSH transport support") -# Optional external dependency: libgssapi -IF (USE_GSSAPI) - FIND_PACKAGE(GSSAPI) +# Optional external dependency: ntlmclient +IF (USE_NTLMCLIENT) + SET(GIT_NTLM 1) + ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/ntlmclient" "${libgit2_BINARY_DIR}/deps/ntlmclient") + LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/ntlmclient") + LIST(APPEND LIBGIT2_OBJECTS "$") ENDIF() -IF (GSSAPI_FOUND) - SET(GIT_GSSAPI 1) - LIST(APPEND LIBGIT2_LIBS ${GSSAPI_LIBRARIES}) -ENDIF() -ADD_FEATURE_INFO(SPNEGO GIT_GSSAPI "SPNEGO authentication support") +ADD_FEATURE_INFO(ntlmclient GIT_NTLM "NTLM authentication support for Unix") + +# Optional external dependency: GSSAPI + +INCLUDE(SelectGSSAPI) # Optional external dependency: iconv IF (USE_ICONV) @@ -417,8 +274,6 @@ FILE(GLOB SRC_H # On Windows use specific platform sources IF (WIN32 AND NOT CYGWIN) - ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0600) - IF(MSVC) SET(WIN_RC "win32/git2.rc") ENDIF() @@ -427,15 +282,29 @@ IF (WIN32 AND NOT CYGWIN) ELSEIF (AMIGA) ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R -DNO_MMAP) ELSE() - IF (VALGRIND) - ADD_DEFINITIONS(-DNO_MMAP) - ENDIF() FILE(GLOB SRC_OS unix/*.c unix/*.h) ENDIF() + +IF (USE_LEAK_CHECKER STREQUAL "valgrind") + ADD_DEFINITIONS(-DVALGRIND) +ENDIF() + FILE(GLOB SRC_GIT2 *.c *.h + allocators/*.c allocators/*.h streams/*.c streams/*.h transports/*.c transports/*.h xdiff/*.c xdiff/*.h) +IF(APPLE) + # The old Secure Transport API has been deprecated in macOS 10.15. + SET_SOURCE_FILES_PROPERTIES(streams/stransport.c PROPERTIES COMPILE_FLAGS -Wno-deprecated) +ENDIF() + +# the xdiff dependency is not (yet) warning-free, disable warnings as +# errors for the xdiff sources until we've sorted them out +IF(MSVC) + SET_SOURCE_FILES_PROPERTIES(xdiff/xdiffi.c PROPERTIES COMPILE_FLAGS -WX-) + SET_SOURCE_FILES_PROPERTIES(xdiff/xutils.c PROPERTIES COMPILE_FLAGS -WX-) +ENDIF() # Determine architecture of the machine IF (CMAKE_SIZEOF_VOID_P EQUAL 8) @@ -457,16 +326,8 @@ SET_TARGET_PROPERTIES(git2internal PROPERTIES C_STANDARD 90) IDE_SPLIT_SOURCES(git2internal) LIST(APPEND LIBGIT2_OBJECTS $) -IF (${CMAKE_VERSION} VERSION_LESS 2.8.12) - INCLUDE_DIRECTORIES(${LIBGIT2_INCLUDES}) - INCLUDE_DIRECTORIES(SYSTEM ${LIBGIT2_SYSTEM_INCLUDES}) -ELSE() - TARGET_INCLUDE_DIRECTORIES(git2internal - PRIVATE ${LIBGIT2_INCLUDES} - PUBLIC ${libgit2_SOURCE_DIR}/include) - TARGET_INCLUDE_DIRECTORIES(git2internal - SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) -ENDIF() +TARGET_INCLUDE_DIRECTORIES(git2internal PRIVATE ${LIBGIT2_INCLUDES} PUBLIC ${libgit2_SOURCE_DIR}/include) +TARGET_INCLUDE_DIRECTORIES(git2internal SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) SET(LIBGIT2_OBJECTS ${LIBGIT2_OBJECTS} PARENT_SCOPE) SET(LIBGIT2_INCLUDES ${LIBGIT2_INCLUDES} PARENT_SCOPE) @@ -508,10 +369,13 @@ IF (SONAME) ENDIF() ENDIF() -LIST(REMOVE_DUPLICATES LIBGIT2_PC_REQUIRES) -STRING(REPLACE ";" " " LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES}") -STRING(REPLACE ";" " " LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS}") -CONFIGURE_FILE(${libgit2_SOURCE_DIR}/libgit2.pc.in ${libgit2_BINARY_DIR}/libgit2.pc @ONLY) +PKG_BUILD_CONFIG(NAME libgit2 + VERSION ${LIBGIT2_VERSION_STRING} + DESCRIPTION "The git library, take 2" + LIBS_SELF git2 + PRIVATE_LIBS ${LIBGIT2_PC_LIBS} + REQUIRES ${LIBGIT2_PC_REQUIRES} +) IF (MSVC_IDE) # Precompiled headers @@ -521,10 +385,9 @@ ENDIF () # Install INSTALL(TARGETS git2 - RUNTIME DESTINATION ${BIN_INSTALL_DIR} - LIBRARY DESTINATION ${LIB_INSTALL_DIR} - ARCHIVE DESTINATION ${LIB_INSTALL_DIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) -INSTALL(FILES ${libgit2_BINARY_DIR}/libgit2.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig ) -INSTALL(DIRECTORY ${libgit2_SOURCE_DIR}/include/git2 DESTINATION ${INCLUDE_INSTALL_DIR} ) -INSTALL(FILES ${libgit2_SOURCE_DIR}/include/git2.h DESTINATION ${INCLUDE_INSTALL_DIR} ) +INSTALL(DIRECTORY ${libgit2_SOURCE_DIR}/include/git2 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +INSTALL(FILES ${libgit2_SOURCE_DIR}/include/git2.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/alloc.c b/src/alloc.c index 8d4a19476..51c4d8029 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -7,11 +7,8 @@ #include "alloc.h" -#if defined(GIT_MSVC_CRTDBG) -# include "win32/w32_crtdbg_stacktrace.h" -#else -# include "stdalloc.h" -#endif +#include "allocators/stdalloc.h" +#include "allocators/win32_crtdbg.h" git_allocator git__allocator; @@ -44,12 +41,3 @@ int git_allocator_setup(git_allocator *allocator) memcpy(&git__allocator, allocator, sizeof(*allocator)); return 0; } - -#if !defined(GIT_MSVC_CRTDBG) -int git_win32_crtdbg_init_allocator(git_allocator *allocator) -{ - GIT_UNUSED(allocator); - git_error_set(GIT_EINVALID, "crtdbg memory allocator not available"); - return -1; -} -#endif diff --git a/src/stdalloc.c b/src/allocators/stdalloc.c similarity index 94% rename from src/stdalloc.c rename to src/allocators/stdalloc.c index c6d4ec80c..c4938e32b 100644 --- a/src/stdalloc.c +++ b/src/allocators/stdalloc.c @@ -88,11 +88,10 @@ static void *stdalloc__reallocarray(void *ptr, size_t nelem, size_t elsize, cons { size_t newsize; - GIT_UNUSED(file); - GIT_UNUSED(line); + if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize)) + return NULL; - return GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize) ? - NULL : realloc(ptr, newsize); + return stdalloc__realloc(ptr, newsize, file, line); } static void *stdalloc__mallocarray(size_t nelem, size_t elsize, const char *file, int line) diff --git a/src/stdalloc.h b/src/allocators/stdalloc.h similarity index 80% rename from src/stdalloc.h rename to src/allocators/stdalloc.h index a8927a507..fa23fe6e3 100644 --- a/src/stdalloc.h +++ b/src/allocators/stdalloc.h @@ -5,13 +5,13 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_stdalloc_h__ -#define INCLUDE_stdalloc_h__ - -#include "alloc.h" +#ifndef INCLUDE_allocators_stdalloc_h__ +#define INCLUDE_allocators_stdalloc_h__ #include "common.h" +#include "alloc.h" + int git_stdalloc_init_allocator(git_allocator *allocator); #endif diff --git a/src/allocators/win32_crtdbg.c b/src/allocators/win32_crtdbg.c new file mode 100644 index 000000000..1187e2fcd --- /dev/null +++ b/src/allocators/win32_crtdbg.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "win32_crtdbg.h" + +#if defined(GIT_MSVC_CRTDBG) + +#include "win32/w32_crtdbg_stacktrace.h" + +static void *crtdbg__malloc(size_t len, const char *file, int line) +{ + void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static void *crtdbg__calloc(size_t nelem, size_t elsize, const char *file, int line) +{ + void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static char *crtdbg__strdup(const char *str, const char *file, int line) +{ + char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); + if (!ptr) git_error_set_oom(); + return ptr; +} + +static char *crtdbg__strndup(const char *str, size_t n, const char *file, int line) +{ + size_t length = 0, alloclength; + char *ptr; + + length = p_strnlen(str, n); + + if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || + !(ptr = crtdbg__malloc(alloclength, file, line))) + return NULL; + + if (length) + memcpy(ptr, str, length); + + ptr[length] = '\0'; + + return ptr; +} + +static char *crtdbg__substrdup(const char *start, size_t n, const char *file, int line) +{ + char *ptr; + size_t alloclen; + + if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || + !(ptr = crtdbg__malloc(alloclen, file, line))) + return NULL; + + memcpy(ptr, start, n); + ptr[n] = '\0'; + return ptr; +} + +static void *crtdbg__realloc(void *ptr, size_t size, const char *file, int line) +{ + void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); + if (!new_ptr) git_error_set_oom(); + return new_ptr; +} + +static void *crtdbg__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) +{ + size_t newsize; + + if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize)) + return NULL; + + return crtdbg__realloc(ptr, newsize, file, line); +} + +static void *crtdbg__mallocarray(size_t nelem, size_t elsize, const char *file, int line) +{ + return crtdbg__reallocarray(NULL, nelem, elsize, file, line); +} + +static void crtdbg__free(void *ptr) +{ + free(ptr); +} + +int git_win32_crtdbg_init_allocator(git_allocator *allocator) +{ + allocator->gmalloc = crtdbg__malloc; + allocator->gcalloc = crtdbg__calloc; + allocator->gstrdup = crtdbg__strdup; + allocator->gstrndup = crtdbg__strndup; + allocator->gsubstrdup = crtdbg__substrdup; + allocator->grealloc = crtdbg__realloc; + allocator->greallocarray = crtdbg__reallocarray; + allocator->gmallocarray = crtdbg__mallocarray; + allocator->gfree = crtdbg__free; + return 0; +} + +#else + +int git_win32_crtdbg_init_allocator(git_allocator *allocator) +{ + GIT_UNUSED(allocator); + git_error_set(GIT_EINVALID, "crtdbg memory allocator not available"); + return -1; +} + +#endif diff --git a/src/transports/cred.h b/src/allocators/win32_crtdbg.h similarity index 61% rename from src/transports/cred.h rename to src/allocators/win32_crtdbg.h index ed5821c55..754c6b6fb 100644 --- a/src/transports/cred.h +++ b/src/allocators/win32_crtdbg.h @@ -4,13 +4,14 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_transports_cred_h__ -#define INCLUDE_transports_cred_h__ + +#ifndef INCLUDE_allocators_crtdbg_h +#define INCLUDE_allocators_crtdbg_h #include "common.h" -#include "git2/transport.h" +#include "alloc.h" -const char *git_cred__username(git_cred *cred); +int git_win32_crtdbg_init_allocator(git_allocator *allocator); #endif diff --git a/src/apply.c b/src/apply.c index d72aa8374..fc60e1418 100644 --- a/src/apply.c +++ b/src/apply.c @@ -7,8 +7,6 @@ #include "apply.h" -#include - #include "git2/apply.h" #include "git2/patch.h" #include "git2/filter.h" @@ -18,15 +16,12 @@ #include "git2/repository.h" #include "array.h" #include "patch.h" -#include "fileops.h" +#include "futils.h" #include "delta.h" #include "zstream.h" #include "reader.h" #include "index.h" -#define apply_err(...) \ - ( git_error_set(GIT_ERROR_PATCH, __VA_ARGS__), GIT_EAPPLYFAIL ) - typedef struct { /* The lines that we allocate ourself are allocated out of the pool. * (Lines may have been allocated out of the diff.) @@ -35,6 +30,18 @@ typedef struct { git_vector lines; } patch_image; +static int apply_err(const char *fmt, ...) GIT_FORMAT_PRINTF(1, 2); +static int apply_err(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + git_error_vset(GIT_ERROR_PATCH, fmt, ap); + va_end(ap); + + return GIT_EAPPLYFAIL; +} + static void patch_line_init( git_diff_line *out, const char *in, @@ -59,7 +66,7 @@ static int patch_image_init_fromstr( git_pool_init(&out->pool, sizeof(git_diff_line)); for (start = in; start < in + in_len; start = end) { - end = memchr(start, '\n', in_len); + end = memchr(start, '\n', in_len - (start - in)); if (end == NULL) end = in + in_len; @@ -199,23 +206,34 @@ static int apply_hunk( for (i = 0; i < hunk->line_count; i++) { size_t linenum = hunk->line_start + i; - git_diff_line *line = git_array_get(patch->lines, linenum); + git_diff_line *line = git_array_get(patch->lines, linenum), *prev; if (!line) { error = apply_err("preimage does not contain line %"PRIuZ, linenum); goto done; } - if (line->origin == GIT_DIFF_LINE_CONTEXT || - line->origin == GIT_DIFF_LINE_DELETION) { - if ((error = git_vector_insert(&preimage.lines, line)) < 0) - goto done; - } - - if (line->origin == GIT_DIFF_LINE_CONTEXT || - line->origin == GIT_DIFF_LINE_ADDITION) { - if ((error = git_vector_insert(&postimage.lines, line)) < 0) - goto done; + switch (line->origin) { + case GIT_DIFF_LINE_CONTEXT_EOFNL: + case GIT_DIFF_LINE_DEL_EOFNL: + case GIT_DIFF_LINE_ADD_EOFNL: + prev = i ? git_array_get(patch->lines, linenum - 1) : NULL; + if (prev && prev->content[prev->content_len - 1] == '\n') + prev->content_len -= 1; + break; + case GIT_DIFF_LINE_CONTEXT: + if ((error = git_vector_insert(&preimage.lines, line)) < 0 || + (error = git_vector_insert(&postimage.lines, line)) < 0) + goto done; + break; + case GIT_DIFF_LINE_DELETION: + if ((error = git_vector_insert(&preimage.lines, line)) < 0) + goto done; + break; + case GIT_DIFF_LINE_ADDITION: + if ((error = git_vector_insert(&postimage.lines, line)) < 0) + goto done; + break; } } @@ -439,7 +457,6 @@ static int apply_one( git_filemode_t pre_filemode; git_index_entry pre_entry, post_entry; bool skip_preimage = false; - size_t pos; int error; if ((error = git_patch_from_diff(&patch, diff, i)) < 0) @@ -464,8 +481,7 @@ static int apply_one( */ if (delta->status != GIT_DELTA_RENAMED && delta->status != GIT_DELTA_ADDED) { - pos = git_strmap_lookup_index(removed_paths, delta->old_file.path); - if (git_strmap_valid_index(removed_paths, pos)) { + if (git_strmap_exists(removed_paths, delta->old_file.path)) { error = apply_err("path '%s' has been renamed or deleted", delta->old_file.path); goto done; } @@ -534,7 +550,7 @@ static int apply_one( if (delta->status != GIT_DELTA_DELETED) { if ((error = git_apply__patch(&post_contents, &filename, &mode, pre_contents.ptr, pre_contents.size, patch, opts)) < 0 || - (error = git_blob_create_frombuffer(&post_id, repo, + (error = git_blob_create_from_buffer(&post_id, repo, post_contents.ptr, post_contents.size)) < 0) goto done; @@ -549,7 +565,7 @@ static int apply_one( if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_DELETED) - git_strmap_insert(removed_paths, delta->old_file.path, (char *)delta->old_file.path, &error); + error = git_strmap_set(removed_paths, delta->old_file.path, (char *) delta->old_file.path); if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_ADDED) @@ -577,7 +593,7 @@ static int apply_deltas( size_t i; int error = 0; - if (git_strmap_alloc(&removed_paths) < 0) + if (git_strmap_new(&removed_paths) < 0) return -1; for (i = 0; i < git_diff_num_deltas(diff); i++) { @@ -631,9 +647,12 @@ int git_apply_to_tree( for (i = 0; i < git_diff_num_deltas(diff); i++) { delta = git_diff_get_delta(diff, i); - if ((error = git_index_remove(postimage, - delta->old_file.path, 0)) < 0) - goto done; + if (delta->status == GIT_DELTA_DELETED || + delta->status == GIT_DELTA_RENAMED) { + if ((error = git_index_remove(postimage, + delta->old_file.path, 0)) < 0) + goto done; + } } if ((error = apply_deltas(repo, pre_reader, NULL, post_reader, postimage, diff, &opts)) < 0) @@ -747,6 +766,13 @@ done: return error; } +int git_apply_options_init(git_apply_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_apply_options, GIT_APPLY_OPTIONS_INIT); + return 0; +} + /* * Handle the three application options ("locations"): * @@ -817,13 +843,17 @@ int git_apply( (error = git_reader_for_index(&post_reader, repo, postimage)) < 0) goto done; - if ((error = git_repository_index(&index, repo)) < 0 || - (error = git_indexwriter_init(&indexwriter, index)) < 0) - goto done; + if (!(opts.flags & GIT_APPLY_CHECK)) + if ((error = git_repository_index(&index, repo)) < 0 || + (error = git_indexwriter_init(&indexwriter, index)) < 0) + goto done; if ((error = apply_deltas(repo, pre_reader, preimage, post_reader, postimage, diff, &opts)) < 0) goto done; + if ((opts.flags & GIT_APPLY_CHECK)) + goto done; + switch (location) { case GIT_APPLY_LOCATION_BOTH: error = git_apply__to_workdir(repo, diff, preimage, postimage, location, &opts); diff --git a/src/attr.c b/src/attr.c index 1f2643345..bd517cde3 100644 --- a/src/attr.c +++ b/src/attr.c @@ -19,18 +19,18 @@ const char *git_attr__true = "[internal]__TRUE__"; const char *git_attr__false = "[internal]__FALSE__"; const char *git_attr__unset = "[internal]__UNSET__"; -git_attr_t git_attr_value(const char *attr) +git_attr_value_t git_attr_value(const char *attr) { if (attr == NULL || attr == git_attr__unset) - return GIT_ATTR_UNSPECIFIED_T; + return GIT_ATTR_VALUE_UNSPECIFIED; if (attr == git_attr__true) - return GIT_ATTR_TRUE_T; + return GIT_ATTR_VALUE_TRUE; if (attr == git_attr__false) - return GIT_ATTR_FALSE_T; + return GIT_ATTR_VALUE_FALSE; - return GIT_ATTR_VALUE_T; + return GIT_ATTR_VALUE_STRING; } static int collect_attr_files( @@ -215,7 +215,7 @@ int git_attr_foreach( return -1; if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 || - (error = git_strmap_alloc(&seen)) < 0) + (error = git_strmap_new(&seen)) < 0) goto cleanup; git_vector_foreach(&files, i, file) { @@ -227,8 +227,7 @@ int git_attr_foreach( if (git_strmap_exists(seen, assign->name)) continue; - git_strmap_insert(seen, assign->name, assign, &error); - if (error < 0) + if ((error = git_strmap_set(seen, assign->name, assign)) < 0) goto cleanup; error = callback(assign->name, assign->value, payload); @@ -253,15 +252,16 @@ static int preload_attr_file( git_attr_session *attr_session, git_attr_file_source source, const char *base, - const char *file) + const char *file, + bool allow_macros) { int error; git_attr_file *preload = NULL; if (!file) return 0; - if (!(error = git_attr_cache__get( - &preload, repo, attr_session, source, base, file, git_attr_file__parse_buffer))) + if (!(error = git_attr_cache__get(&preload, repo, attr_session, source, base, file, + git_attr_file__parse_buffer, allow_macros))) git_attr_file__free(preload); return error; @@ -305,12 +305,15 @@ static int system_attr_file( return 0; } -static int attr_setup(git_repository *repo, git_attr_session *attr_session) +static int attr_setup( + git_repository *repo, + git_attr_session *attr_session, + uint32_t flags) { - int error = 0; - const char *workdir = git_repository_workdir(repo); - git_index *idx = NULL; git_buf path = GIT_BUF_INIT; + git_index *idx = NULL; + const char *workdir; + int error = 0; if (attr_session && attr_session->init_setup) return 0; @@ -318,41 +321,43 @@ static int attr_setup(git_repository *repo, git_attr_session *attr_session) if ((error = git_attr_cache__init(repo)) < 0) return error; - /* preload attribute files that could contain macros so the - * definitions will be available for later file parsing + /* + * Preload attribute files that could contain macros so the + * definitions will be available for later file parsing. */ - error = system_attr_file(&path, attr_session); + if ((error = system_attr_file(&path, attr_session)) < 0 || + (error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE, + NULL, path.ptr, true)) < 0) { + if (error != GIT_ENOTFOUND) + goto out; + } - if (error == 0) - error = preload_attr_file( - repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, path.ptr); - - if (error != GIT_ENOTFOUND) + if ((error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE, + NULL, git_repository_attr_cache(repo)->cfg_attr_file, true)) < 0) goto out; - if ((error = preload_attr_file( - repo, attr_session, GIT_ATTR_FILE__FROM_FILE, - NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0) - goto out; + git_buf_clear(&path); /* git_repository_item_path expects an empty buffer, because it uses git_buf_set */ + if ((error = git_repository_item_path(&path, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || + (error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE, + path.ptr, GIT_ATTR_FILE_INREPO, true)) < 0) { + if (error != GIT_ENOTFOUND) + goto out; + } - if ((error = git_repository_item_path(&path, - repo, GIT_REPOSITORY_ITEM_INFO)) < 0) - goto out; - - if ((error = preload_attr_file( - repo, attr_session, GIT_ATTR_FILE__FROM_FILE, - path.ptr, GIT_ATTR_FILE_INREPO)) < 0) - goto out; - - if (workdir != NULL && - (error = preload_attr_file( - repo, attr_session, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0) - goto out; + if ((workdir = git_repository_workdir(repo)) != NULL && + (error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE, + workdir, GIT_ATTR_FILE, true)) < 0) + goto out; if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || - (error = preload_attr_file( - repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0) + (error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, + NULL, GIT_ATTR_FILE, true)) < 0) + goto out; + + if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0 && + (error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_HEAD, + NULL, GIT_ATTR_FILE, true)) < 0) goto out; if (attr_session) @@ -431,6 +436,9 @@ static int attr_decide_sources( break; } + if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) + srcs[count++] = GIT_ATTR_FILE__FROM_HEAD; + return count; } @@ -440,13 +448,14 @@ static int push_attr_file( git_vector *list, git_attr_file_source source, const char *base, - const char *filename) + const char *filename, + bool allow_macros) { int error = 0; git_attr_file *file = NULL; error = git_attr_cache__get(&file, repo, attr_session, - source, base, filename, git_attr_file__parse_buffer); + source, base, filename, git_attr_file__parse_buffer, allow_macros); if (error < 0) return error; @@ -461,16 +470,18 @@ static int push_attr_file( static int push_one_attr(void *ref, const char *path) { - int error = 0, n_src, i; attr_walk_up_info *info = (attr_walk_up_info *)ref; - git_attr_file_source src[2]; + git_attr_file_source src[GIT_ATTR_FILE_NUM_SOURCES]; + int error = 0, n_src, i; + bool allow_macros; n_src = attr_decide_sources( info->flags, info->workdir != NULL, info->index != NULL, src); + allow_macros = info->workdir ? !strcmp(info->workdir, path) : false; for (i = 0; !error && i < n_src; ++i) - error = push_attr_file(info->repo, info->attr_session, - info->files, src[i], path, GIT_ATTR_FILE); + error = push_attr_file(info->repo, info->attr_session, info->files, + src[i], path, GIT_ATTR_FILE, allow_macros); return error; } @@ -499,7 +510,7 @@ static int collect_attr_files( const char *workdir = git_repository_workdir(repo); attr_walk_up_info info = { NULL }; - if ((error = attr_setup(repo, attr_session)) < 0) + if ((error = attr_setup(repo, attr_session, flags)) < 0) return error; /* Resolve path in a non-bare repo */ @@ -517,15 +528,12 @@ static int collect_attr_files( * - $GIT_PREFIX/etc/gitattributes */ - error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO); - if (error < 0) - goto cleanup; - - error = push_attr_file( - repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, - attrfile.ptr, GIT_ATTR_FILE_INREPO); - if (error < 0) - goto cleanup; + if ((error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || + (error = push_attr_file(repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, + attrfile.ptr, GIT_ATTR_FILE_INREPO, true)) < 0) { + if (error != GIT_ENOTFOUND) + goto cleanup; + } info.repo = repo; info.attr_session = attr_session; @@ -544,9 +552,8 @@ static int collect_attr_files( goto cleanup; if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) { - error = push_attr_file( - repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, - NULL, git_repository_attr_cache(repo)->cfg_attr_file); + error = push_attr_file(repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, + NULL, git_repository_attr_cache(repo)->cfg_attr_file, true); if (error < 0) goto cleanup; } @@ -555,9 +562,8 @@ static int collect_attr_files( error = system_attr_file(&dir, attr_session); if (!error) - error = push_attr_file( - repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, - NULL, dir.ptr); + error = push_attr_file(repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, + NULL, dir.ptr, true); else if (error == GIT_ENOTFOUND) error = 0; } diff --git a/src/attr_file.c b/src/attr_file.c index 8619647a3..82da5268f 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -10,10 +10,12 @@ #include "repository.h" #include "filebuf.h" #include "attrcache.h" +#include "buf_text.h" #include "git2/blob.h" #include "git2/tree.h" #include "blob.h" #include "index.h" +#include "wildmatch.h" #include static void attr_file_free(git_attr_file *file) @@ -103,14 +105,22 @@ int git_attr_file__load( git_attr_session *attr_session, git_attr_file_entry *entry, git_attr_file_source source, - git_attr_file_parser parser) + git_attr_file_parser parser, + bool allow_macros) { int error = 0; + git_tree *tree = NULL; + git_tree_entry *tree_entry = NULL; git_blob *blob = NULL; git_buf content = GIT_BUF_INIT; + const char *content_str; git_attr_file *file; struct stat st; bool nonexistent = false; + int bom_offset; + git_bom_t bom; + git_oid id; + git_object_size_t blobsize; *out = NULL; @@ -119,9 +129,6 @@ int git_attr_file__load( /* in-memory attribute file doesn't need data */ break; case GIT_ATTR_FILE__FROM_INDEX: { - git_oid id; - git_off_t blobsize; - if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 || (error = git_blob_lookup(&blob, repo, &id)) < 0) return error; @@ -151,6 +158,25 @@ int git_attr_file__load( break; } + case GIT_ATTR_FILE__FROM_HEAD: { + if ((error = git_repository_head_tree(&tree, repo)) < 0 || + (error = git_tree_entry_bypath(&tree_entry, tree, entry->path)) < 0 || + (error = git_blob_lookup(&blob, repo, git_tree_entry_id(tree_entry))) < 0) + goto cleanup; + + /* + * Do not assume that data straight from the ODB is NULL-terminated; + * copy the contents of a file to a buffer to work on. + */ + blobsize = git_blob_rawsize(blob); + + GIT_ERROR_CHECK_BLOBSIZE(blobsize); + if ((error = git_buf_put(&content, + git_blob_rawcontent(blob), (size_t)blobsize)) < 0) + goto cleanup; + + break; + } default: git_error_set(GIT_ERROR_INVALID, "unknown file source %d", source); return -1; @@ -159,13 +185,20 @@ int git_attr_file__load( if ((error = git_attr_file__new(&file, entry, source)) < 0) goto cleanup; + /* advance over a UTF8 BOM */ + content_str = git_buf_cstr(&content); + bom_offset = git_buf_text_detect_bom(&bom, &content); + + if (bom == GIT_BOM_UTF8) + content_str += bom_offset; + /* store the key of the attr_reader; don't bother with cache * invalidation during the same attr reader session. */ if (attr_session) file->session_key = attr_session->key; - if (parser && (error = parser(repo, file, git_buf_cstr(&content))) < 0) { + if (parser && (error = parser(repo, file, content_str, allow_macros)) < 0) { git_attr_file__free(file); goto cleanup; } @@ -175,6 +208,8 @@ int git_attr_file__load( file->nonexistent = 1; else if (source == GIT_ATTR_FILE__FROM_INDEX) git_oid_cpy(&file->cache_data.oid, git_blob_id(blob)); + else if (source == GIT_ATTR_FILE__FROM_HEAD) + git_oid_cpy(&file->cache_data.oid, git_tree_id(tree)); else if (source == GIT_ATTR_FILE__FROM_FILE) git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st); /* else always cacheable */ @@ -183,6 +218,8 @@ int git_attr_file__load( cleanup: git_blob_free(blob); + git_tree_entry_free(tree_entry); + git_tree_free(tree); git_buf_dispose(&content); return error; @@ -223,6 +260,19 @@ int git_attr_file__out_of_date( return (git_oid__cmp(&file->cache_data.oid, &id) != 0); } + case GIT_ATTR_FILE__FROM_HEAD: { + git_tree *tree; + int error; + + if ((error = git_repository_head_tree(&tree, repo)) < 0) + return error; + + error = git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)); + + git_tree_free(tree); + return error; + } + default: git_error_set(GIT_ERROR_INVALID, "invalid file type %d", file->source); return -1; @@ -237,16 +287,15 @@ static bool parse_optimized_patterns( const char *pattern); int git_attr_file__parse_buffer( - git_repository *repo, git_attr_file *attrs, const char *data) + git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros) { - int error = 0; const char *scan = data, *context = NULL; git_attr_rule *rule = NULL; + int error = 0; - /* if subdir file path, convert context for file paths */ - if (attrs->entry && - git_path_root(attrs->entry->path) < 0 && - !git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE)) + /* If subdir file path, convert context for file paths */ + if (attrs->entry && git_path_root(attrs->entry->path) < 0 && + !git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE)) context = attrs->entry->path; if (git_mutex_lock(&attrs->lock) < 0) { @@ -255,38 +304,38 @@ int git_attr_file__parse_buffer( } while (!error && *scan) { - /* allocate rule if needed */ - if (!rule && !(rule = git__calloc(1, sizeof(*rule)))) { - error = -1; - break; - } + /* Allocate rule if needed, otherwise re-use previous rule */ + if (!rule) { + rule = git__calloc(1, sizeof(*rule)); + GIT_ERROR_CHECK_ALLOC(rule); + } else + git_attr_rule__clear(rule); - rule->match.flags = - GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO; + rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO; - /* parse the next "pattern attr attr attr" line */ - if (!(error = git_attr_fnmatch__parse( - &rule->match, &attrs->pool, context, &scan)) && - !(error = git_attr_assignment__parse( - repo, &attrs->pool, &rule->assigns, &scan))) + /* Parse the next "pattern attr attr attr" line */ + if ((error = git_attr_fnmatch__parse(&rule->match, &attrs->pool, context, &scan)) < 0 || + (error = git_attr_assignment__parse(repo, &attrs->pool, &rule->assigns, &scan)) < 0) { - if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) - /* TODO: warning if macro found in file below repo root */ - error = git_attr_cache__insert_macro(repo, rule); - else - error = git_vector_insert(&attrs->rules, rule); + if (error != GIT_ENOTFOUND) + goto out; + error = 0; + continue; } - /* if the rule wasn't a pattern, on to the next */ - if (error < 0) { - git_attr_rule__clear(rule); /* reset rule contents */ - if (error == GIT_ENOTFOUND) - error = 0; - } else { - rule = NULL; /* vector now "owns" the rule */ - } + if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) { + /* TODO: warning if macro found in file below repo root */ + if (!allow_macros) + continue; + if ((error = git_attr_cache__insert_macro(repo, rule)) < 0) + goto out; + } else if ((error = git_vector_insert(&attrs->rules, rule)) < 0) + goto out; + + rule = NULL; } +out: git_mutex_unlock(&attrs->lock); git_attr_rule__free(rule); @@ -333,33 +382,28 @@ int git_attr_file__lookup_one( int git_attr_file__load_standalone(git_attr_file **out, const char *path) { - int error; - git_attr_file *file; git_buf content = GIT_BUF_INIT; + git_attr_file *file = NULL; + int error; - error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE); - if (error < 0) - return error; + if ((error = git_futils_readbuffer(&content, path)) < 0) + goto out; - error = git_attr_cache__alloc_file_entry( - &file->entry, NULL, path, &file->pool); - if (error < 0) { - git_attr_file__free(file); - return error; - } - /* because the cache entry is allocated from the file's own pool, we + /* + * Because the cache entry is allocated from the file's own pool, we * don't have to free it - freeing file+pool will free cache entry, too. */ - if (!(error = git_futils_readbuffer(&content, path))) { - error = git_attr_file__parse_buffer(NULL, file, content.ptr); - git_buf_dispose(&content); - } + if ((error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE)) < 0 || + (error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 || + (error = git_attr_cache__alloc_file_entry(&file->entry, NULL, path, &file->pool)) < 0) + goto out; + *out = file; +out: if (error < 0) git_attr_file__free(file); - else - *out = file; + git_buf_dispose(&content); return error; } @@ -390,18 +434,13 @@ bool git_attr_fnmatch__match( } if (match->flags & GIT_ATTR_FNMATCH_ICASE) - flags |= FNM_CASEFOLD; - if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR) - flags |= FNM_LEADING_DIR; + flags |= WM_CASEFOLD; if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) { filename = relpath; - flags |= FNM_PATHNAME; + flags |= WM_PATHNAME; } else { filename = path->basename; - - if (path->is_dir) - flags |= FNM_LEADING_DIR; } if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) { @@ -416,8 +455,6 @@ bool git_attr_fnmatch__match( path->basename == relpath) return false; - flags |= FNM_LEADING_DIR; - /* fail match if this is a file with same name as ignored folder */ samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? !strcasecmp(match->pattern, relpath) : @@ -426,10 +463,10 @@ bool git_attr_fnmatch__match( if (samename) return false; - return (p_fnmatch(match->pattern, relpath, flags) != FNM_NOMATCH); + return (wildmatch(match->pattern, relpath, flags) == WM_MATCH); } - return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH); + return (wildmatch(match->pattern, filename, flags) == WM_MATCH); } bool git_attr_rule__match( @@ -548,6 +585,61 @@ void git_attr_path__free(git_attr_path *info) * "cat-file.c" but not "mozilla-sha1/sha1.c". */ +/* + * Determine the length of trailing spaces. Escaped spaces do not count as + * trailing whitespace. + */ +static size_t trailing_space_length(const char *p, size_t len) +{ + size_t n, i; + for (n = len; n; n--) { + if (p[n-1] != ' ' && p[n-1] != '\t') + break; + + /* + * Count escape-characters before space. In case where it's an + * even number of escape characters, then the escape char itself + * is escaped and the whitespace is an unescaped whitespace. + * Otherwise, the last escape char is not escaped and the + * whitespace in an escaped whitespace. + */ + i = n; + while (i > 1 && p[i-2] == '\\') + i--; + if ((n - i) % 2) + break; + } + return len - n; +} + +static size_t unescape_spaces(char *str) +{ + char *scan, *pos = str; + bool escaped = false; + + if (!str) + return 0; + + for (scan = str; *scan; scan++) { + if (!escaped && *scan == '\\') { + escaped = true; + continue; + } + + /* Only insert the escape character for escaped non-spaces */ + if (escaped && !git__isspace(*scan)) + *pos++ = '\\'; + + *pos++ = *scan; + escaped = false; + } + + if (pos != scan) + *pos = '\0'; + + return (pos - str); +} + /* * This will return 0 if the spec was filled out, * GIT_ENOTFOUND if the fnmatch does not require matching, or @@ -561,6 +653,7 @@ int git_attr_fnmatch__parse( { const char *pattern, *scan; int slash_count, allow_space; + bool escaped; assert(spec && base && *base); @@ -572,8 +665,11 @@ int git_attr_fnmatch__parse( pattern = *base; - while (git__isspace(*pattern)) pattern++; - if (!*pattern || *pattern == '#') { + while (!allow_space && git__isspace(*pattern)) + pattern++; + + if (!*pattern || *pattern == '#' || *pattern == '\n' || + (*pattern == '\r' && *(pattern + 1) == '\n')) { *base = git__next_line(pattern); return GIT_ENOTFOUND; } @@ -588,29 +684,33 @@ int git_attr_fnmatch__parse( if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) { spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; - if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0) - spec->flags |= GIT_ATTR_FNMATCH_LEADINGDIR; pattern++; } slash_count = 0; + escaped = false; + /* Scan until a non-escaped whitespace. */ for (scan = pattern; *scan != '\0'; ++scan) { - /* scan until (non-escaped) white space */ - if (git__isspace(*scan) && *(scan - 1) != '\\') { - if (!allow_space || (*scan != ' ' && *scan != '\t' && *scan != '\r')) - break; - } + char c = *scan; - if (*scan == '/') { + if (c == '\\' && !escaped) { + escaped = true; + continue; + } else if (git__isspace(c) && !escaped) { + if (!allow_space || (c != ' ' && c != '\t' && c != '\r')) + break; + } else if (c == '/') { spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH; slash_count++; - if (pattern == scan) + + if (slash_count == 1 && pattern == scan) pattern++; - } - /* remember if we see an unescaped wildcard in pattern */ - else if (git__iswildcard(*scan) && - (scan == pattern || (*(scan - 1) != '\\'))) + } else if (git__iswildcard(c) && !escaped) { + /* remember if we see an unescaped wildcard in pattern */ spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD; + } + + escaped = false; } *base = scan; @@ -628,9 +728,10 @@ int git_attr_fnmatch__parse( return GIT_ENOTFOUND; /* Remove trailing spaces. */ - while (pattern[spec->length - 1] == ' ' || pattern[spec->length - 1] == '\t') - if (--spec->length == 0) - return GIT_ENOTFOUND; + spec->length -= trailing_space_length(pattern, spec->length); + + if (spec->length == 0) + return GIT_ENOTFOUND; if (pattern[spec->length - 1] == '/') { spec->length--; @@ -638,14 +739,6 @@ int git_attr_fnmatch__parse( if (--slash_count <= 0) spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; } - if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0 && - spec->length >= 2 && - pattern[spec->length - 1] == '*' && - pattern[spec->length - 2] == '/') { - spec->length -= 2; - spec->flags = spec->flags | GIT_ATTR_FNMATCH_LEADINGDIR; - /* leave FULLPATH match on, however */ - } if (context) { char *slash = strrchr(context, '/'); @@ -664,9 +757,8 @@ int git_attr_fnmatch__parse( *base = git__next_line(pattern); return -1; } else { - /* strip '\' that might have be used for internal whitespace */ - spec->length = git__unescape(spec->pattern); - /* TODO: convert remaining '\' into '/' for POSIX ??? */ + /* strip '\' that might have been used for internal whitespace */ + spec->length = unescape_spaces(spec->pattern); } return 0; @@ -859,6 +951,7 @@ int git_attr_session__init(git_attr_session *session, git_repository *repo) { assert(repo); + memset(session, 0, sizeof(*session)); session->key = git_atomic_inc(&repo->attr_session_key); return 0; diff --git a/src/attr_file.h b/src/attr_file.h index fedf55af5..2b6b1d623 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -14,7 +14,7 @@ #include "vector.h" #include "pool.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" #define GIT_ATTR_FILE ".gitattributes" #define GIT_ATTR_FILE_INREPO "attributes" @@ -32,19 +32,17 @@ #define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8) #define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9) #define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10) -#define GIT_ATTR_FNMATCH_LEADINGDIR (1U << 11) -#define GIT_ATTR_FNMATCH_NOLEADINGDIR (1U << 12) #define GIT_ATTR_FNMATCH__INCOMING \ - (GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | \ - GIT_ATTR_FNMATCH_ALLOWMACRO | GIT_ATTR_FNMATCH_NOLEADINGDIR) + (GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO) typedef enum { GIT_ATTR_FILE__IN_MEMORY = 0, GIT_ATTR_FILE__FROM_FILE = 1, GIT_ATTR_FILE__FROM_INDEX = 2, + GIT_ATTR_FILE__FROM_HEAD = 3, - GIT_ATTR_FILE_NUM_SOURCES = 3 + GIT_ATTR_FILE_NUM_SOURCES = 4 } git_attr_file_source; extern const char *git_attr__true; @@ -134,7 +132,8 @@ extern int git_attr_get_many_with_session( typedef int (*git_attr_file_parser)( git_repository *repo, git_attr_file *file, - const char *data); + const char *data, + bool allow_macros); /* * git_attr_file API @@ -153,7 +152,8 @@ int git_attr_file__load( git_attr_session *attr_session, git_attr_file_entry *ce, git_attr_file_source source, - git_attr_file_parser parser); + git_attr_file_parser parser, + bool allow_macros); int git_attr_file__load_standalone( git_attr_file **out, const char *path); @@ -162,7 +162,7 @@ int git_attr_file__out_of_date( git_repository *repo, git_attr_session *session, git_attr_file *file); int git_attr_file__parse_buffer( - git_repository *repo, git_attr_file *attrs, const char *data); + git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros); int git_attr_file__clear_rules( git_attr_file *file, bool need_lock); diff --git a/src/attrcache.c b/src/attrcache.c index cb8a4a4e9..f02dd9d1d 100644 --- a/src/attrcache.c +++ b/src/attrcache.c @@ -33,12 +33,7 @@ GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache) GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry( git_attr_cache *cache, const char *path) { - size_t pos = git_strmap_lookup_index(cache->files, path); - - if (git_strmap_valid_index(cache->files, pos)) - return git_strmap_value_at(cache->files, pos); - else - return NULL; + return git_strmap_get(cache->files, path); } int git_attr_cache__alloc_file_entry( @@ -59,7 +54,7 @@ int git_attr_cache__alloc_file_entry( cachesize++; } - ce = git_pool_mallocz(pool, (uint32_t)cachesize); + ce = git_pool_mallocz(pool, cachesize); GIT_ERROR_CHECK_ALLOC(ce); if (baselen) { @@ -80,18 +75,16 @@ int git_attr_cache__alloc_file_entry( static int attr_cache_make_entry( git_attr_file_entry **out, git_repository *repo, const char *path) { - int error = 0; git_attr_cache *cache = git_repository_attr_cache(repo); git_attr_file_entry *entry = NULL; + int error; - error = git_attr_cache__alloc_file_entry( - &entry, git_repository_workdir(repo), path, &cache->pool); + if ((error = git_attr_cache__alloc_file_entry(&entry, git_repository_workdir(repo), + path, &cache->pool)) < 0) + return error; - if (!error) { - git_strmap_insert(cache->files, entry->path, entry, &error); - if (error > 0) - error = 0; - } + if ((error = git_strmap_set(cache->files, entry->path, entry)) < 0) + return error; *out = entry; return error; @@ -215,7 +208,8 @@ int git_attr_cache__get( git_attr_file_source source, const char *base, const char *filename, - git_attr_file_parser parser) + git_attr_file_parser parser, + bool allow_macros) { int error = 0; git_attr_cache *cache = git_repository_attr_cache(repo); @@ -228,7 +222,7 @@ int git_attr_cache__get( /* load file if we don't have one or if existing one is out of date */ if (!file || (error = git_attr_file__out_of_date(repo, attr_session, file)) > 0) - error = git_attr_file__load(&updated, repo, attr_session, entry, source, parser); + error = git_attr_file__load(&updated, repo, attr_session, entry, source, parser, allow_macros); /* if we loaded the file, insert into and/or update cache */ if (updated) { @@ -265,19 +259,15 @@ bool git_attr_cache__is_cached( const char *filename) { git_attr_cache *cache = git_repository_attr_cache(repo); - git_strmap *files; - size_t pos; git_attr_file_entry *entry; + git_strmap *files; if (!cache || !(files = cache->files)) return false; - pos = git_strmap_lookup_index(files, filename); - if (!git_strmap_valid_index(files, pos)) + if ((entry = git_strmap_get(files, filename)) == NULL) return false; - entry = git_strmap_value_at(files, pos); - return entry && (entry->file[source] != NULL); } @@ -400,8 +390,8 @@ int git_attr_cache__init(git_repository *repo) /* allocate hashtable for attribute and ignore file contents, * hashtable for attribute macros, and string pool */ - if ((ret = git_strmap_alloc(&cache->files)) < 0 || - (ret = git_strmap_alloc(&cache->macros)) < 0) + if ((ret = git_strmap_new(&cache->files)) < 0 || + (ret = git_strmap_new(&cache->macros)) < 0) goto cancel; git_pool_init(&cache->pool, 1); @@ -413,7 +403,7 @@ int git_attr_cache__init(git_repository *repo) git_config_free(cfg); /* insert default macros */ - return git_attr_add_macro(repo, "binary", "-diff -crlf -text"); + return git_attr_add_macro(repo, "binary", "-diff -merge -text -crlf"); cancel: attr_cache__free(cache); @@ -421,7 +411,7 @@ cancel: return ret; } -void git_attr_cache_flush(git_repository *repo) +int git_attr_cache_flush(git_repository *repo) { git_attr_cache *cache; @@ -430,40 +420,50 @@ void git_attr_cache_flush(git_repository *repo) */ if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL) attr_cache__free(cache); + + return 0; } int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro) { git_attr_cache *cache = git_repository_attr_cache(repo); - git_strmap *macros = cache->macros; - int error; + git_attr_rule *preexisting; + bool locked = false; + int error = 0; - /* TODO: generate warning log if (macro->assigns.length == 0) */ - if (macro->assigns.length == 0) - return 0; - - if (attr_cache_lock(cache) < 0) { - git_error_set(GIT_ERROR_OS, "unable to get attr cache lock"); - error = -1; - } else { - git_strmap_insert(macros, macro->match.pattern, macro, &error); - git_mutex_unlock(&cache->lock); + /* + * Callers assume that if we return success, that the + * macro will have been adopted by the attributes cache. + * Thus, we have to free the macro here if it's not being + * added to the cache. + * + * TODO: generate warning log if (macro->assigns.length == 0) + */ + if (macro->assigns.length == 0) { + git_attr_rule__free(macro); + goto out; } - return (error < 0) ? -1 : 0; + if ((error = attr_cache_lock(cache)) < 0) + goto out; + locked = true; + + if ((preexisting = git_strmap_get(cache->macros, macro->match.pattern)) != NULL) + git_attr_rule__free(preexisting); + + if ((error = git_strmap_set(cache->macros, macro->match.pattern, macro)) < 0) + goto out; + +out: + if (locked) + attr_cache_unlock(cache); + return error; } git_attr_rule *git_attr_cache__lookup_macro( git_repository *repo, const char *name) { git_strmap *macros = git_repository_attr_cache(repo)->macros; - size_t pos; - pos = git_strmap_lookup_index(macros, name); - - if (!git_strmap_valid_index(macros, pos)) - return NULL; - - return (git_attr_rule *)git_strmap_value_at(macros, pos); + return git_strmap_get(macros, name); } - diff --git a/src/attrcache.h b/src/attrcache.h index f528911ea..4b1d5ce31 100644 --- a/src/attrcache.h +++ b/src/attrcache.h @@ -34,7 +34,8 @@ extern int git_attr_cache__get( git_attr_file_source source, const char *base, const char *filename, - git_attr_file_parser parser); + git_attr_file_parser parser, + bool allow_macros); extern bool git_attr_cache__is_cached( git_repository *repo, diff --git a/src/blame.c b/src/blame.c index be10c15d6..23c21027a 100644 --- a/src/blame.c +++ b/src/blame.c @@ -204,7 +204,7 @@ static int normalize_options( memcpy(out, in, sizeof(git_blame_options)); /* No newest_commit => HEAD */ - if (git_oid_iszero(&out->newest_commit)) { + if (git_oid_is_zero(&out->newest_commit)) { if (git_reference_name_to_id(&out->newest_commit, repo, "HEAD") < 0) { return -1; } @@ -268,7 +268,7 @@ static git_blame_hunk *split_hunk_in_vector( static int index_blob_lines(git_blame *blame) { const char *buf = blame->final_buf; - git_off_t len = blame->final_buf_size; + size_t len = blame->final_buf_size; int num = 0, incomplete = 0, bol = 1; size_t *i; @@ -335,8 +335,15 @@ static int blame_internal(git_blame *blame) if ((error = load_blob(blame)) < 0 || (error = git_blame__get_origin(&o, blame, blame->final, blame->path)) < 0) goto cleanup; + + if (git_blob_rawsize(blame->final_blob) > SIZE_MAX) { + git_error_set(GIT_ERROR_NOMEMORY, "blob is too large to blame"); + error = -1; + goto cleanup; + } + blame->final_buf = git_blob_rawcontent(blame->final_blob); - blame->final_buf_size = git_blob_rawsize(blame->final_blob); + blame->final_buf_size = (size_t)git_blob_rawsize(blame->final_blob); ent = git__calloc(1, sizeof(git_blame__entry)); GIT_ERROR_CHECK_ALLOC(ent); @@ -408,7 +415,7 @@ on_error: static bool hunk_is_bufferblame(git_blame_hunk *hunk) { - return git_oid_iszero(&hunk->final_commit_id); + return hunk && git_oid_is_zero(&hunk->final_commit_id); } static int buffer_hunk_cb( @@ -524,9 +531,14 @@ int git_blame_buffer( return 0; } -int git_blame_init_options(git_blame_options *opts, unsigned int version) +int git_blame_options_init(git_blame_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_blame_options, GIT_BLAME_OPTIONS_INIT); return 0; } + +int git_blame_init_options(git_blame_options *opts, unsigned int version) +{ + return git_blame_options_init(opts, version); +} diff --git a/src/blame.h b/src/blame.h index b31d2dc20..4141e2bb5 100644 --- a/src/blame.h +++ b/src/blame.h @@ -84,7 +84,7 @@ struct git_blame { git_blame__entry *ent; int num_lines; const char *final_buf; - git_off_t final_buf_size; + size_t final_buf_size; }; git_blame *git_blame__alloc( diff --git a/src/blame_git.c b/src/blame_git.c index 06e7d648e..a9157c4ed 100644 --- a/src/blame_git.c +++ b/src/blame_git.c @@ -219,7 +219,7 @@ static void dup_entry(git_blame__entry *dst, git_blame__entry *src) * split_overlap() divided an existing blame e into up to three parts in split. * Adjust the linked list of blames in the scoreboard to reflect the split. */ -static void split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e) +static int split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e) { git_blame__entry *new_entry; @@ -229,11 +229,13 @@ static void split_blame(git_blame *blame, git_blame__entry *split, git_blame__en /* The last part -- me */ new_entry = git__malloc(sizeof(*new_entry)); + GIT_ERROR_CHECK_ALLOC(new_entry); memcpy(new_entry, &(split[2]), sizeof(git_blame__entry)); add_blame_entry(blame, new_entry); /* ... and the middle part -- parent */ new_entry = git__malloc(sizeof(*new_entry)); + GIT_ERROR_CHECK_ALLOC(new_entry); memcpy(new_entry, &(split[1]), sizeof(git_blame__entry)); add_blame_entry(blame, new_entry); } else if (!split[0].suspect && !split[2].suspect) { @@ -246,15 +248,19 @@ static void split_blame(git_blame *blame, git_blame__entry *split, git_blame__en /* me and then parent */ dup_entry(e, &split[0]); new_entry = git__malloc(sizeof(*new_entry)); + GIT_ERROR_CHECK_ALLOC(new_entry); memcpy(new_entry, &(split[1]), sizeof(git_blame__entry)); add_blame_entry(blame, new_entry); } else { /* parent and then me */ dup_entry(e, &split[1]); new_entry = git__malloc(sizeof(*new_entry)); + GIT_ERROR_CHECK_ALLOC(new_entry); memcpy(new_entry, &(split[2]), sizeof(git_blame__entry)); add_blame_entry(blame, new_entry); } + + return 0; } /* @@ -272,7 +278,7 @@ static void decref_split(git_blame__entry *split) * Helper for blame_chunk(). blame_entry e is known to overlap with the patch * hunk; split it and pass blame to the parent. */ -static void blame_overlap( +static int blame_overlap( git_blame *blame, git_blame__entry *e, size_t tlno, @@ -284,8 +290,11 @@ static void blame_overlap( split_overlap(split, e, tlno, plno, same, parent); if (split[1].suspect) - split_blame(blame, split, e); + if (split_blame(blame, split, e) < 0) + return -1; decref_split(split); + + return 0; } /* @@ -293,7 +302,7 @@ static void blame_overlap( * e and its parent. Find and split the overlap, and pass blame to the * overlapping part to the parent. */ -static void blame_chunk( +static int blame_chunk( git_blame *blame, size_t tlno, size_t plno, @@ -309,9 +318,12 @@ static void blame_chunk( if (same <= e->s_lno) continue; if (tlno < e->s_lno + e->num_lines) { - blame_overlap(blame, e, tlno, plno, same, parent); + if (blame_overlap(blame, e, tlno, plno, same, parent) < 0) + return -1; } } + + return 0; } static int my_emit( @@ -321,7 +333,8 @@ static int my_emit( { blame_chunk_cb_data *d = (blame_chunk_cb_data *)cb_data; - blame_chunk(d->blame, d->tlno, d->plno, start_b, d->target, d->parent); + if (blame_chunk(d->blame, d->tlno, d->plno, start_b, d->target, d->parent) < 0) + return -1; d->plno = start_a + count_a; d->tlno = start_b + count_b; @@ -400,7 +413,8 @@ static int pass_blame_to_parent( return -1; /* The reset (i.e. anything after tlno) are the same as the parent */ - blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent); + if (blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent) < 0) + return -1; return 0; } diff --git a/src/blob.c b/src/blob.c index 566d24b8e..5e734e2c1 100644 --- a/src/blob.c +++ b/src/blob.c @@ -25,18 +25,18 @@ const void *git_blob_rawcontent(const git_blob *blob) return git_odb_object_data(blob->data.odb); } -git_off_t git_blob_rawsize(const git_blob *blob) +git_object_size_t git_blob_rawsize(const git_blob *blob) { assert(blob); if (blob->raw) return blob->data.raw.size; else - return (git_off_t)git_odb_object_size(blob->data.odb); + return (git_object_size_t)git_odb_object_size(blob->data.odb); } int git_blob__getbuf(git_buf *buffer, git_blob *blob) { - git_off_t size = git_blob_rawsize(blob); + git_object_size_t size = git_blob_rawsize(blob); GIT_ERROR_CHECK_BLOBSIZE(size); return git_buf_set(buffer, git_blob_rawcontent(blob), (size_t)size); @@ -70,7 +70,7 @@ int git_blob__parse(void *_blob, git_odb_object *odb_obj) return 0; } -int git_blob_create_frombuffer( +int git_blob_create_from_buffer( git_oid *id, git_repository *repo, const void *buffer, size_t len) { int error; @@ -91,13 +91,13 @@ int git_blob_create_frombuffer( } static int write_file_stream( - git_oid *id, git_odb *odb, const char *path, git_off_t file_size) + git_oid *id, git_odb *odb, const char *path, git_object_size_t file_size) { int fd, error; char buffer[FILEIO_BUFSIZE]; git_odb_stream *stream = NULL; ssize_t read_len = -1; - git_off_t written = 0; + git_object_size_t written = 0; if ((error = git_odb_open_wstream( &stream, odb, file_size, GIT_OBJECT_BLOB)) < 0) @@ -129,7 +129,7 @@ static int write_file_stream( static int write_file_filtered( git_oid *id, - git_off_t *size, + git_object_size_t *size, git_odb *odb, const char *full_path, git_filter_list *fl) @@ -184,7 +184,7 @@ int git_blob__create_from_paths( int error; struct stat st; git_odb *odb = NULL; - git_off_t size; + git_object_size_t size; mode_t mode; git_buf path = GIT_BUF_INIT; @@ -263,13 +263,13 @@ done: return error; } -int git_blob_create_fromworkdir( +int git_blob_create_from_workdir( git_oid *id, git_repository *repo, const char *path) { return git_blob__create_from_paths(id, NULL, repo, NULL, path, 0, true); } -int git_blob_create_fromdisk( +int git_blob_create_from_disk( git_oid *id, git_repository *repo, const char *path) { int error; @@ -325,7 +325,7 @@ static int blob_writestream_write(git_writestream *_stream, const char *buffer, return git_filebuf_write(&stream->fbuf, buffer, len); } -int git_blob_create_fromstream(git_writestream **out, git_repository *repo, const char *hintpath) +int git_blob_create_from_stream(git_writestream **out, git_repository *repo, const char *hintpath) { int error; git_buf path = GIT_BUF_INIT; @@ -364,7 +364,7 @@ cleanup: return error; } -int git_blob_create_fromstream_commit(git_oid *out, git_writestream *_stream) +int git_blob_create_from_stream_commit(git_oid *out, git_writestream *_stream) { int error; blob_writestream *stream = (blob_writestream *) _stream; @@ -389,7 +389,7 @@ cleanup: int git_blob_is_binary(const git_blob *blob) { git_buf content = GIT_BUF_INIT; - git_off_t size; + git_object_size_t size; assert(blob); @@ -400,25 +400,40 @@ int git_blob_is_binary(const git_blob *blob) return git_buf_text_is_binary(&content); } -int git_blob_filtered_content( +int git_blob_filter( git_buf *out, git_blob *blob, const char *path, - int check_for_binary_data) + git_blob_filter_options *given_opts) { int error = 0; git_filter_list *fl = NULL; + git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; + git_filter_flag_t flags = GIT_FILTER_DEFAULT; assert(blob && path && out); git_buf_sanitize(out); - if (check_for_binary_data && git_blob_is_binary(blob)) + GIT_ERROR_CHECK_VERSION( + given_opts, GIT_BLOB_FILTER_OPTIONS_VERSION, "git_blob_filter_options"); + + if (given_opts != NULL) + memcpy(&opts, given_opts, sizeof(git_blob_filter_options)); + + if ((opts.flags & GIT_BLOB_FILTER_CHECK_FOR_BINARY) != 0 && + git_blob_is_binary(blob)) return 0; + if ((opts.flags & GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES) != 0) + flags |= GIT_FILTER_NO_SYSTEM_ATTRIBUTES; + + if ((opts.flags & GIT_BLOB_FILTER_ATTTRIBUTES_FROM_HEAD) != 0) + flags |= GIT_FILTER_ATTRIBUTES_FROM_HEAD; + if (!(error = git_filter_list_load( &fl, git_blob_owner(blob), blob, path, - GIT_FILTER_TO_WORKTREE, GIT_FILTER_DEFAULT))) { + GIT_FILTER_TO_WORKTREE, flags))) { error = git_filter_list_apply_to_blob(out, fl, blob); @@ -427,3 +442,52 @@ int git_blob_filtered_content( return error; } + +/* Deprecated functions */ + +int git_blob_create_frombuffer( + git_oid *id, git_repository *repo, const void *buffer, size_t len) +{ + return git_blob_create_from_buffer(id, repo, buffer, len); +} + +int git_blob_create_fromworkdir(git_oid *id, git_repository *repo, const char *relative_path) +{ + return git_blob_create_from_workdir(id, repo, relative_path); +} + +int git_blob_create_fromdisk(git_oid *id, git_repository *repo, const char *path) +{ + return git_blob_create_from_disk(id, repo, path); +} + +int git_blob_create_fromstream( + git_writestream **out, + git_repository *repo, + const char *hintpath) +{ + return git_blob_create_from_stream(out, repo, hintpath); +} + +int git_blob_create_fromstream_commit( + git_oid *out, + git_writestream *stream) +{ + return git_blob_create_from_stream_commit(out, stream); +} + +int git_blob_filtered_content( + git_buf *out, + git_blob *blob, + const char *path, + int check_for_binary_data) +{ + git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; + + if (check_for_binary_data) + opts.flags |= GIT_BLOB_FILTER_CHECK_FOR_BINARY; + else + opts.flags &= ~GIT_BLOB_FILTER_CHECK_FOR_BINARY; + + return git_blob_filter(out, blob, path, &opts); +} diff --git a/src/blob.h b/src/blob.h index b9aa33004..e5770991e 100644 --- a/src/blob.h +++ b/src/blob.h @@ -12,7 +12,7 @@ #include "git2/blob.h" #include "repository.h" #include "odb.h" -#include "fileops.h" +#include "futils.h" struct git_blob { git_object object; @@ -21,7 +21,7 @@ struct git_blob { git_odb_object *odb; struct { const char *data; - git_off_t size; + git_object_size_t size; } raw; } data; unsigned int raw:1; diff --git a/src/branch.c b/src/branch.c index 61ed03e2e..8926c8956 100644 --- a/src/branch.c +++ b/src/branch.c @@ -22,7 +22,7 @@ static int retrieve_branch_reference( git_reference **branch_reference_out, git_repository *repo, const char *branch_name, - int is_remote) + bool is_remote) { git_reference *branch = NULL; int error = 0; @@ -153,10 +153,20 @@ done: int git_branch_is_checked_out(const git_reference *branch) { - assert(branch && git_reference_is_branch(branch)); + git_repository *repo; + int flags = 0; - return git_repository_foreach_head(git_reference_owner(branch), - branch_equals, (void *) branch) == 1; + assert(branch); + + if (!git_reference_is_branch(branch)) + return 0; + + repo = git_reference_owner(branch); + + if (git_repository_is_bare(repo)) + flags |= GIT_REPOSITORY_FOREACH_HEAD_SKIP_REPO; + + return git_repository_foreach_head(repo, branch_equals, flags, (void *) branch) == 1; } int git_branch_delete(git_reference *branch) @@ -324,9 +334,23 @@ int git_branch_lookup( const char *branch_name, git_branch_t branch_type) { + int error = -1; assert(ref_out && repo && branch_name); - return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE); + switch (branch_type) { + case GIT_BRANCH_LOCAL: + case GIT_BRANCH_REMOTE: + error = retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE); + break; + case GIT_BRANCH_ALL: + error = retrieve_branch_reference(ref_out, repo, branch_name, false); + if (error == GIT_ENOTFOUND) + error = retrieve_branch_reference(ref_out, repo, branch_name, true); + break; + default: + assert(false); + } + return error; } int git_branch_name( @@ -573,35 +597,36 @@ on_error: return -1; } -int git_branch_set_upstream(git_reference *branch, const char *upstream_name) +int git_branch_set_upstream(git_reference *branch, const char *branch_name) { - git_buf key = GIT_BUF_INIT, value = GIT_BUF_INIT; + git_buf key = GIT_BUF_INIT, remote_name = GIT_BUF_INIT, merge_refspec = GIT_BUF_INIT; git_reference *upstream; git_repository *repo; git_remote *remote = NULL; git_config *config; - const char *name, *shortname; + const char *refname, *shortname; int local, error; const git_refspec *fetchspec; - name = git_reference_name(branch); - if (!git_reference__is_branch(name)) - return not_a_local_branch(name); + refname = git_reference_name(branch); + if (!git_reference__is_branch(refname)) + return not_a_local_branch(refname); if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0) return -1; - shortname = name + strlen(GIT_REFS_HEADS_DIR); + shortname = refname + strlen(GIT_REFS_HEADS_DIR); - if (upstream_name == NULL) + /* We're unsetting, delegate and bail-out */ + if (branch_name == NULL) return unset_upstream(config, shortname); repo = git_reference_owner(branch); - /* First we need to figure out whether it's a branch or remote-tracking */ - if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_LOCAL) == 0) + /* First we need to resolve name to a branch */ + if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_LOCAL) == 0) local = 1; - else if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_REMOTE) == 0) + else if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_REMOTE) == 0) local = 0; else { git_error_set(GIT_ERROR_REFERENCE, @@ -610,60 +635,63 @@ int git_branch_set_upstream(git_reference *branch, const char *upstream_name) } /* - * If it's local, the remote is "." and the branch name is - * simply the refname. Otherwise we need to figure out what - * the remote-tracking branch's name on the remote is and use - * that. + * If it's a local-tracking branch, its remote is "." (as "the local + * repository"), and the branch name is simply the refname. + * Otherwise we need to figure out what the remote-tracking branch's + * name on the remote is and use that. */ if (local) - error = git_buf_puts(&value, "."); + error = git_buf_puts(&remote_name, "."); else - error = git_branch_remote_name(&value, repo, git_reference_name(upstream)); + error = git_branch_remote_name(&remote_name, repo, git_reference_name(upstream)); if (error < 0) goto on_error; + /* Update the upsteam branch config with the new name */ if (git_buf_printf(&key, "branch.%s.remote", shortname) < 0) goto on_error; - if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0) + if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&remote_name)) < 0) goto on_error; if (local) { - git_buf_clear(&value); - if (git_buf_puts(&value, git_reference_name(upstream)) < 0) + /* A local branch uses the upstream refname directly */ + if (git_buf_puts(&merge_refspec, git_reference_name(upstream)) < 0) goto on_error; } else { - /* Get the remoe-tracking branch's refname in its repo */ - if (git_remote_lookup(&remote, repo, git_buf_cstr(&value)) < 0) + /* We transform the upstream branch name according to the remote's refspecs */ + if (git_remote_lookup(&remote, repo, git_buf_cstr(&remote_name)) < 0) goto on_error; fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream)); - git_buf_clear(&value); - if (!fetchspec || git_refspec_rtransform(&value, fetchspec, git_reference_name(upstream)) < 0) + if (!fetchspec || git_refspec_rtransform(&merge_refspec, fetchspec, git_reference_name(upstream)) < 0) goto on_error; git_remote_free(remote); remote = NULL; } + /* Update the merge branch config with the refspec */ git_buf_clear(&key); if (git_buf_printf(&key, "branch.%s.merge", shortname) < 0) goto on_error; - if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0) + if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&merge_refspec)) < 0) goto on_error; git_reference_free(upstream); git_buf_dispose(&key); - git_buf_dispose(&value); + git_buf_dispose(&remote_name); + git_buf_dispose(&merge_refspec); return 0; on_error: git_reference_free(upstream); git_buf_dispose(&key); - git_buf_dispose(&value); + git_buf_dispose(&remote_name); + git_buf_dispose(&merge_refspec); git_remote_free(remote); return -1; diff --git a/src/buffer.c b/src/buffer.c index 51fb48a45..328fdfe7f 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -18,7 +18,8 @@ char git_buf__initbuf[1]; char git_buf__oom[1]; #define ENSURE_SIZE(b, d) \ - if ((d) > (b)->asize && git_buf_grow((b), (d)) < 0)\ + if ((b)->ptr == git_buf__oom || \ + ((d) > (b)->asize && git_buf_grow((b), (d)) < 0))\ return -1; @@ -58,20 +59,26 @@ int git_buf_try_grow( new_ptr = NULL; } else { new_size = buf->asize; + /* + * Grow the allocated buffer by 1.5 to allow + * re-use of memory holes resulting from the + * realloc. If this is still too small, then just + * use the target size. + */ + if ((new_size = (new_size << 1) - (new_size >> 1)) < target_size) + new_size = target_size; new_ptr = buf->ptr; } - /* grow the buffer size by 1.5, until it's big enough - * to fit our target size */ - while (new_size < target_size) - new_size = (new_size << 1) - (new_size >> 1); - /* round allocation up to multiple of 8 */ new_size = (new_size + 7) & ~7; if (new_size < buf->size) { - if (mark_oom) + if (mark_oom) { + if (buf->ptr && buf->ptr != git_buf__initbuf) + git__free(buf->ptr); buf->ptr = git_buf__oom; + } git_error_set_oom(); return -1; @@ -560,6 +567,11 @@ void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf) data[copylen] = '\0'; } +void git_buf_consume_bytes(git_buf *buf, size_t len) +{ + git_buf_consume(buf, buf->ptr + len); +} + void git_buf_consume(git_buf *buf, const char *end) { if (end > buf->ptr && end <= buf->ptr + buf->size) { diff --git a/src/buffer.h b/src/buffer.h index 7910b6338..6b717d2e9 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -113,6 +113,7 @@ int git_buf_puts(git_buf *buf, const char *string); int git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); int git_buf_vprintf(git_buf *buf, const char *format, va_list ap); void git_buf_clear(git_buf *buf); +void git_buf_consume_bytes(git_buf *buf, size_t len); void git_buf_consume(git_buf *buf, const char *end); void git_buf_truncate(git_buf *buf, size_t len); void git_buf_shorten(git_buf *buf, size_t amount); diff --git a/src/cache.c b/src/cache.c index 66107eaa9..af42b3959 100644 --- a/src/cache.c +++ b/src/cache.c @@ -65,12 +65,15 @@ void git_cache_dump_stats(git_cache *cache) int git_cache_init(git_cache *cache) { memset(cache, 0, sizeof(*cache)); - cache->map = git_oidmap_alloc(); - GIT_ERROR_CHECK_ALLOC(cache->map); + + if ((git_oidmap_new(&cache->map)) < 0) + return -1; + if (git_rwlock_init(&cache->lock)) { git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock"); return -1; } + return 0; } @@ -101,7 +104,7 @@ void git_cache_clear(git_cache *cache) git_rwlock_wrunlock(&cache->lock); } -void git_cache_free(git_cache *cache) +void git_cache_dispose(git_cache *cache) { git_cache_clear(cache); git_oidmap_free(cache->map); @@ -112,28 +115,30 @@ void git_cache_free(git_cache *cache) /* Called with lock */ static void cache_evict_entries(git_cache *cache) { - uint32_t seed = rand(); - size_t evict_count = 8; + size_t evict_count = git_cache_size(cache) / 2048, i; ssize_t evicted_memory = 0; + if (evict_count < 8) + evict_count = 8; + /* do not infinite loop if there's not enough entries to evict */ if (evict_count > git_cache_size(cache)) { clear_cache(cache); return; } + i = 0; while (evict_count > 0) { - size_t pos = seed++ % git_oidmap_end(cache->map); + git_cached_obj *evict; + const git_oid *key; - if (git_oidmap_has_data(cache->map, pos)) { - git_cached_obj *evict = git_oidmap_value_at(cache->map, pos); + if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER) + break; - evict_count--; - evicted_memory += evict->size; - git_cached_obj_decref(evict); - - git_oidmap_delete_at(cache->map, pos); - } + evict_count--; + evicted_memory += evict->size; + git_oidmap_delete(cache->map, key); + git_cached_obj_decref(evict); } cache->used_memory -= evicted_memory; @@ -148,16 +153,12 @@ static bool cache_should_store(git_object_t object_type, size_t object_size) static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags) { - size_t pos; - git_cached_obj *entry = NULL; + git_cached_obj *entry; if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0) return NULL; - pos = git_oidmap_lookup_index(cache->map, oid); - if (git_oidmap_valid_index(cache->map, pos)) { - entry = git_oidmap_value_at(cache->map, pos); - + if ((entry = git_oidmap_get(cache->map, oid)) != NULL) { if (flags && entry->flags != flags) { entry = NULL; } else { @@ -172,7 +173,7 @@ static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags) static void *cache_store(git_cache *cache, git_cached_obj *entry) { - size_t pos; + git_cached_obj *stored_entry; git_cached_obj_incref(entry); @@ -191,14 +192,9 @@ static void *cache_store(git_cache *cache, git_cached_obj *entry) if (git_cache__current_storage.val > git_cache__max_storage) cache_evict_entries(cache); - pos = git_oidmap_lookup_index(cache->map, &entry->oid); - /* not found */ - if (!git_oidmap_valid_index(cache->map, pos)) { - int rval; - - git_oidmap_insert(cache->map, &entry->oid, entry, &rval); - if (rval >= 0) { + if ((stored_entry = git_oidmap_get(cache->map, &entry->oid)) == NULL) { + if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) { git_cached_obj_incref(entry); cache->used_memory += entry->size; git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size); @@ -206,19 +202,20 @@ static void *cache_store(git_cache *cache, git_cached_obj *entry) } /* found */ else { - git_cached_obj *stored_entry = git_oidmap_value_at(cache->map, pos); - if (stored_entry->flags == entry->flags) { git_cached_obj_decref(entry); git_cached_obj_incref(stored_entry); entry = stored_entry; } else if (stored_entry->flags == GIT_CACHE_STORE_RAW && - entry->flags == GIT_CACHE_STORE_PARSED) { - git_cached_obj_decref(stored_entry); - git_cached_obj_incref(entry); - - git_oidmap_set_key_at(cache->map, pos, &entry->oid); - git_oidmap_set_value_at(cache->map, pos, entry); + entry->flags == GIT_CACHE_STORE_PARSED) { + if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) { + git_cached_obj_decref(stored_entry); + git_cached_obj_incref(entry); + } else { + git_cached_obj_decref(entry); + git_cached_obj_incref(stored_entry); + entry = stored_entry; + } } else { /* NO OP */ } diff --git a/src/cache.h b/src/cache.h index d2386eea6..1e6179236 100644 --- a/src/cache.h +++ b/src/cache.h @@ -43,7 +43,7 @@ extern git_atomic_ssize git_cache__current_storage; int git_cache_set_max_object_size(git_object_t type, size_t size); int git_cache_init(git_cache *cache); -void git_cache_free(git_cache *cache); +void git_cache_dispose(git_cache *cache); void git_cache_clear(git_cache *cache); void *git_cache_store_raw(git_cache *cache, git_odb_object *entry); diff --git a/src/cc-compat.h b/src/cc-compat.h index 8aaa8bb5a..7ade561f3 100644 --- a/src/cc-compat.h +++ b/src/cc-compat.h @@ -48,6 +48,11 @@ /* Define the printf format specifer to use for size_t output */ #if defined(_MSC_VER) || defined(__MINGW32__) +/* Visual Studio 2012 and prior lack PRId64 entirely */ +# ifndef PRId64 +# define PRId64 "I64d" +# endif + /* The first block is needed to avoid warnings on MingW amd64 */ # if (SIZE_MAX == ULLONG_MAX) # define PRIuZ "I64u" diff --git a/src/checkout.c b/src/checkout.c index fe020894a..5cfa7280b 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -7,8 +7,6 @@ #include "checkout.h" -#include - #include "git2/repository.h" #include "git2/refs.h" #include "git2/tree.h" @@ -1391,7 +1389,7 @@ static bool should_remove_existing(checkout_data *data) { int ignorecase; - if (git_repository__cvar(&ignorecase, data->repo, GIT_CVAR_IGNORECASE) < 0) { + if (git_repository__configmap_lookup(&ignorecase, data->repo, GIT_CONFIGMAP_IGNORECASE) < 0) { ignorecase = 0; } @@ -1893,11 +1891,18 @@ static int checkout_create_the_new( return error; } - if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) { - error = checkout_blob(data, &delta->new_file); - if (error < 0) + if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB && !S_ISLNK(delta->new_file.mode)) { + if ((error = checkout_blob(data, &delta->new_file)) < 0) return error; + data->completed_steps++; + report_progress(data, delta->new_file.path); + } + } + git_vector_foreach(&data->diff->deltas, i, delta) { + if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB && S_ISLNK(delta->new_file.mode)) { + if ((error = checkout_blob(data, &delta->new_file)) < 0) + return error; data->completed_steps++; report_progress(data, delta->new_file.path); } @@ -2463,12 +2468,12 @@ static int checkout_data_init( data->pfx = git_pathspec_prefix(&data->opts.paths); - if ((error = git_repository__cvar( - &data->can_symlink, repo, GIT_CVAR_SYMLINKS)) < 0) + if ((error = git_repository__configmap_lookup( + &data->can_symlink, repo, GIT_CONFIGMAP_SYMLINKS)) < 0) goto cleanup; - if ((error = git_repository__cvar( - &data->respect_filemode, repo, GIT_CVAR_FILEMODE)) < 0) + if ((error = git_repository__configmap_lookup( + &data->respect_filemode, repo, GIT_CONFIGMAP_FILEMODE)) < 0) goto cleanup; if (!data->opts.baseline && !data->opts.baseline_index) { @@ -2518,11 +2523,11 @@ static int checkout_data_init( git_pool_init(&data->pool, 1); if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || - (error = git_vector_init(&data->remove_conflicts, 0, NULL)) < 0 || - (error = git_vector_init(&data->update_conflicts, 0, NULL)) < 0 || - (error = git_buf_puts(&data->target_path, data->opts.target_directory)) < 0 || - (error = git_path_to_dir(&data->target_path)) < 0 || - (error = git_strmap_alloc(&data->mkdir_map)) < 0) + (error = git_vector_init(&data->remove_conflicts, 0, NULL)) < 0 || + (error = git_vector_init(&data->update_conflicts, 0, NULL)) < 0 || + (error = git_buf_puts(&data->target_path, data->opts.target_directory)) < 0 || + (error = git_path_to_dir(&data->target_path)) < 0 || + (error = git_strmap_new(&data->mkdir_map)) < 0) goto cleanup; data->target_len = git_buf_len(&data->target_path); @@ -2791,9 +2796,14 @@ int git_checkout_head( return git_checkout_tree(repo, NULL, opts); } -int git_checkout_init_options(git_checkout_options *opts, unsigned int version) +int git_checkout_options_init(git_checkout_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_checkout_options, GIT_CHECKOUT_OPTIONS_INIT); return 0; } + +int git_checkout_init_options(git_checkout_options *opts, unsigned int version) +{ + return git_checkout_options_init(opts, version); +} diff --git a/src/cherrypick.c b/src/cherrypick.c index 26935c6df..640d11a7b 100644 --- a/src/cherrypick.c +++ b/src/cherrypick.c @@ -30,7 +30,7 @@ static int write_cherrypick_head( int error = 0; if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_CHERRYPICK_HEAD_FILE)) >= 0 && - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) >= 0 && + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) >= 0 && (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0) error = git_filebuf_commit(&file); @@ -51,7 +51,7 @@ static int write_merge_msg( int error = 0; if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) < 0 || (error = git_filebuf_printf(&file, "%s", commit_msg)) < 0) goto cleanup; @@ -221,10 +221,16 @@ done: return error; } -int git_cherrypick_init_options( +int git_cherrypick_options_init( git_cherrypick_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_cherrypick_options, GIT_CHERRYPICK_OPTIONS_INIT); return 0; } + +int git_cherrypick_init_options( + git_cherrypick_options *opts, unsigned int version) +{ + return git_cherrypick_options_init(opts, version); +} diff --git a/src/clone.c b/src/clone.c index 8688da9e4..3d749c32f 100644 --- a/src/clone.c +++ b/src/clone.c @@ -7,8 +7,6 @@ #include "clone.h" -#include - #include "git2/clone.h" #include "git2/remote.h" #include "git2/revparse.h" @@ -19,7 +17,7 @@ #include "git2/tree.h" #include "remote.h" -#include "fileops.h" +#include "futils.h" #include "refs.h" #include "path.h" #include "repository.h" @@ -382,11 +380,12 @@ done: return is_local; } -int git_clone( +static int git__clone( git_repository **out, const char *url, const char *local_path, - const git_clone_options *_options) + const git_clone_options *_options, + int use_existing) { int error = 0; git_repository *repo = NULL; @@ -403,7 +402,7 @@ int git_clone( GIT_ERROR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options"); /* Only clone to a new directory or an empty directory */ - if (git_path_exists(local_path) && !git_path_is_empty_dir(local_path)) { + if (git_path_exists(local_path) && !use_existing && !git_path_is_empty_dir(local_path)) { git_error_set(GIT_ERROR_INVALID, "'%s' exists and is not an empty directory", local_path); return GIT_EEXISTS; @@ -455,13 +454,36 @@ int git_clone( return error; } -int git_clone_init_options(git_clone_options *opts, unsigned int version) +int git_clone( + git_repository **out, + const char *url, + const char *local_path, + const git_clone_options *_options) +{ + return git__clone(out, url, local_path, _options, 0); +} + +int git_clone__submodule( + git_repository **out, + const char *url, + const char *local_path, + const git_clone_options *_options) +{ + return git__clone(out, url, local_path, _options, 1); +} + +int git_clone_options_init(git_clone_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT); return 0; } +int git_clone_init_options(git_clone_options *opts, unsigned int version) +{ + return git_clone_options_init(opts, version); +} + static bool can_link(const char *src, const char *dst, int link) { #ifdef GIT_WIN32 diff --git a/src/clone.h b/src/clone.h index 864b59029..7d73cabd5 100644 --- a/src/clone.h +++ b/src/clone.h @@ -11,6 +11,10 @@ #include "git2/clone.h" +extern int git_clone__submodule(git_repository **out, + const char *url, const char *local_path, + const git_clone_options *_options); + extern int git_clone__should_clone_local(const char *url, git_clone_local_t local); #endif diff --git a/src/commit.c b/src/commit.c index 513fdccaf..cf9902d02 100644 --- a/src/commit.c +++ b/src/commit.c @@ -20,6 +20,7 @@ #include "message.h" #include "refs.h" #include "object.h" +#include "array.h" #include "oidarray.h" void git_commit__free(void *_commit) @@ -80,8 +81,8 @@ on_error: } static int validate_tree_and_parents(git_array_oid_t *parents, git_repository *repo, const git_oid *tree, - git_commit_parent_callback parent_cb, void *parent_payload, - const git_oid *current_id, bool validate) + git_commit_parent_callback parent_cb, void *parent_payload, + const git_oid *current_id, bool validate) { size_t i; int error; @@ -152,8 +153,8 @@ static int git_commit__create_internal( goto cleanup; error = git_commit__create_buffer_internal(&buf, author, committer, - message_encoding, message, tree, - &parents); + message_encoding, message, tree, + &parents); if (error < 0) goto cleanup; @@ -383,15 +384,16 @@ int git_commit_amend( return error; } -int git_commit__parse_raw(void *_commit, const char *data, size_t size) +static int commit_parse(git_commit *commit, const char *data, size_t size, unsigned int flags) { - git_commit *commit = _commit; const char *buffer_start = data, *buffer; const char *buffer_end = buffer_start + size; git_oid parent_id; size_t header_len; git_signature dummy_sig; + assert(commit && data); + buffer = buffer_start; /* Allocate for one, which will allow not to realloc 90% of the time */ @@ -399,8 +401,15 @@ int git_commit__parse_raw(void *_commit, const char *data, size_t size) GIT_ERROR_CHECK_ARRAY(commit->parent_ids); /* The tree is always the first field */ - if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) - goto bad_buffer; + if (!(flags & GIT_COMMIT_PARSE_QUICK)) { + if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) + goto bad_buffer; + } else { + size_t tree_len = strlen("tree ") + GIT_OID_HEXSZ + 1; + if (buffer + tree_len > buffer_end) + goto bad_buffer; + buffer += tree_len; + } /* * TODO: commit grafts! @@ -413,11 +422,13 @@ int git_commit__parse_raw(void *_commit, const char *data, size_t size) git_oid_cpy(new_id, &parent_id); } - commit->author = git__malloc(sizeof(git_signature)); - GIT_ERROR_CHECK_ALLOC(commit->author); + if (!(flags & GIT_COMMIT_PARSE_QUICK)) { + commit->author = git__malloc(sizeof(git_signature)); + GIT_ERROR_CHECK_ALLOC(commit->author); - if (git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n') < 0) - return -1; + if (git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n') < 0) + return -1; + } /* Some tools create multiple author fields, ignore the extra ones */ while (!git__prefixncmp(buffer, buffer_end - buffer, "author ")) { @@ -435,6 +446,9 @@ int git_commit__parse_raw(void *_commit, const char *data, size_t size) if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0) return -1; + if (flags & GIT_COMMIT_PARSE_QUICK) + return 0; + /* Parse add'l header entries */ while (buffer < buffer_end) { const char *eoln = buffer; @@ -477,11 +491,19 @@ bad_buffer: return -1; } +int git_commit__parse_raw(void *commit, const char *data, size_t size) +{ + return commit_parse(commit, data, size, 0); +} + +int git_commit__parse_ext(git_commit *commit, git_odb_object *odb_obj, unsigned int flags) +{ + return commit_parse(commit, git_odb_object_data(odb_obj), git_odb_object_size(odb_obj), flags); +} + int git_commit__parse(void *_commit, git_odb_object *odb_obj) { - return git_commit__parse_raw(_commit, - git_odb_object_data(odb_obj), - git_odb_object_size(odb_obj)); + return git_commit__parse_ext(_commit, odb_obj, 0); } #define GIT_COMMIT_GETTER(_rvalue, _name, _return) \ @@ -582,7 +604,7 @@ const char *git_commit_body(git_commit *commit) break; if (*msg) - commit->body = git__strndup(msg, end - msg + 1); + commit->body = git__strndup(msg, end - msg + 1); } return commit->body; @@ -731,7 +753,7 @@ int git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_r return error; if (obj->cached.type != GIT_OBJECT_COMMIT) { - git_error_set(GIT_ERROR_INVALID, "the requested type does not match the type in ODB"); + git_error_set(GIT_ERROR_INVALID, "the requested type does not match the type in the ODB"); error = GIT_ENOTFOUND; goto cleanup; } @@ -856,6 +878,14 @@ static void format_header_field(git_buf *out, const char *field, const char *con git_buf_putc(out, '\n'); } +static const git_oid *commit_parent_from_commit(size_t n, void *payload) +{ + const git_commit *commit = (const git_commit *) payload; + + return git_array_get(commit->parent_ids, n); + +} + int git_commit_create_with_signature( git_oid *out, git_repository *repo, @@ -868,20 +898,37 @@ int git_commit_create_with_signature( const char *field; const char *header_end; git_buf commit = GIT_BUF_INIT; + git_commit *parsed; + git_array_oid_t parents = GIT_ARRAY_INIT; - /* We start by identifying the end of the commit header */ + /* The first step is to verify that all the tree and parents exist */ + parsed = git__calloc(1, sizeof(git_commit)); + GIT_ERROR_CHECK_ALLOC(parsed); + if ((error = commit_parse(parsed, commit_content, strlen(commit_content), 0)) < 0) + goto cleanup; + + if ((error = validate_tree_and_parents(&parents, repo, &parsed->tree_id, commit_parent_from_commit, parsed, NULL, true)) < 0) + goto cleanup; + + git_array_clear(parents); + + /* Then we start appending by identifying the end of the commit header */ header_end = strstr(commit_content, "\n\n"); if (!header_end) { git_error_set(GIT_ERROR_INVALID, "malformed commit contents"); - return -1; + error = -1; + goto cleanup; } - field = signature_field ? signature_field : "gpgsig"; - /* The header ends after the first LF */ header_end++; git_buf_put(&commit, commit_content, header_end - commit_content); - format_header_field(&commit, field, signature); + + if (signature != NULL) { + field = signature_field ? signature_field : "gpgsig"; + format_header_field(&commit, field, signature); + } + git_buf_puts(&commit, header_end); if (git_buf_oom(&commit)) @@ -894,6 +941,7 @@ int git_commit_create_with_signature( goto cleanup; cleanup: + git_commit__free(parsed); git_buf_dispose(&commit); return error; } diff --git a/src/commit.h b/src/commit.h index 9137a8fad..318ce5cba 100644 --- a/src/commit.h +++ b/src/commit.h @@ -37,4 +37,10 @@ void git_commit__free(void *commit); int git_commit__parse(void *commit, git_odb_object *obj); int git_commit__parse_raw(void *commit, const char *data, size_t size); +typedef enum { + GIT_COMMIT_PARSE_QUICK = (1 << 0), /**< Only parse parents and committer info */ +} git_commit__parse_flags; + +int git_commit__parse_ext(git_commit *commit, git_odb_object *odb_obj, unsigned int flags); + #endif diff --git a/src/commit_list.c b/src/commit_list.c index 78b1f055c..2e103ec22 100644 --- a/src/commit_list.c +++ b/src/commit_list.c @@ -10,6 +10,7 @@ #include "revwalk.h" #include "pool.h" #include "odb.h" +#include "commit.h" int git_commit_list_time_cmp(const void *a, const void *b) { @@ -55,17 +56,6 @@ git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk) return (git_commit_list_node *)git_pool_mallocz(&walk->commit_pool, 1); } -static int commit_error(git_commit_list_node *commit, const char *msg) -{ - char commit_oid[GIT_OID_HEXSZ + 1]; - git_oid_fmt(commit_oid, &commit->oid); - commit_oid[GIT_OID_HEXSZ] = '\0'; - - git_error_set(GIT_ERROR_ODB, "failed to parse commit %s - %s", commit_oid, msg); - - return -1; -} - static git_commit_list_node **alloc_parents( git_revwalk *walk, git_commit_list_node *commit, size_t n_parents) { @@ -111,77 +101,42 @@ git_commit_list_node *git_commit_list_pop(git_commit_list **stack) static int commit_quick_parse( git_revwalk *walk, - git_commit_list_node *commit, - const uint8_t *buffer, - size_t buffer_len) + git_commit_list_node *node, + git_odb_object *obj) { - const size_t parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1; - const uint8_t *buffer_end = buffer + buffer_len; - const uint8_t *parents_start, *committer_start; - int i, parents = 0; - int64_t commit_time; + git_oid *parent_oid; + git_commit *commit; + int error; + size_t i; - buffer += strlen("tree ") + GIT_OID_HEXSZ + 1; + commit = git__calloc(1, sizeof(*commit)); + GIT_ERROR_CHECK_ALLOC(commit); + commit->object.repo = walk->repo; - parents_start = buffer; - while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", strlen("parent ")) == 0) { - parents++; - buffer += parent_len; + if ((error = git_commit__parse_ext(commit, obj, GIT_COMMIT_PARSE_QUICK)) < 0) { + git__free(commit); + return error; } - commit->parents = alloc_parents(walk, commit, parents); - GIT_ERROR_CHECK_ALLOC(commit->parents); - - buffer = parents_start; - for (i = 0; i < parents; ++i) { - git_oid oid; - - if (git_oid_fromstr(&oid, (const char *)buffer + strlen("parent ")) < 0) - return -1; - - commit->parents[i] = git_revwalk__commit_lookup(walk, &oid); - if (commit->parents[i] == NULL) - return -1; - - buffer += parent_len; + if (!git__is_uint16(git_array_size(commit->parent_ids))) { + git__free(commit); + git_error_set(GIT_ERROR_INVALID, "commit has more than 2^16 parents"); + return -1; } - commit->out_degree = (unsigned short)parents; + node->time = commit->committer->when.time; + node->out_degree = (uint16_t) git_array_size(commit->parent_ids); + node->parents = alloc_parents(walk, node, node->out_degree); + GIT_ERROR_CHECK_ALLOC(node->parents); - if ((committer_start = buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) - return commit_error(commit, "object is corrupted"); - - buffer++; - - if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) - return commit_error(commit, "object is corrupted"); - - /* Skip trailing spaces */ - while (buffer > committer_start && git__isspace(*buffer)) - buffer--; - - /* Seek for the beginning of the pack of digits */ - while (buffer > committer_start && git__isdigit(*buffer)) - buffer--; - - /* Skip potential timezone offset */ - if ((buffer > committer_start) && (*buffer == '+' || *buffer == '-')) { - buffer--; - - while (buffer > committer_start && git__isspace(*buffer)) - buffer--; - - while (buffer > committer_start && git__isdigit(*buffer)) - buffer--; + git_array_foreach(commit->parent_ids, i, parent_oid) { + node->parents[i] = git_revwalk__commit_lookup(walk, parent_oid); } - if ((buffer == committer_start) || - (git__strntol64(&commit_time, (char *)(buffer + 1), - buffer_end - buffer + 1, NULL, 10) < 0)) - return commit_error(commit, "cannot parse commit time"); + git_commit__free(commit); + + node->parsed = 1; - commit->time = commit_time; - commit->parsed = 1; return 0; } @@ -200,10 +155,7 @@ int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit) git_error_set(GIT_ERROR_INVALID, "object is no commit object"); error = -1; } else - error = commit_quick_parse( - walk, commit, - (const uint8_t *)git_odb_object_data(obj), - git_odb_object_size(obj)); + error = commit_quick_parse(walk, commit, obj); git_odb_object_free(obj); return error; diff --git a/src/commit_list.h b/src/commit_list.h index a7551a2bc..6a65f8a76 100644 --- a/src/commit_list.h +++ b/src/commit_list.h @@ -33,8 +33,8 @@ typedef struct git_commit_list_node { added:1, flags : FLAG_BITS; - unsigned short in_degree; - unsigned short out_degree; + uint16_t in_degree; + uint16_t out_degree; struct git_commit_list_node **parents; } git_commit_list_node; diff --git a/src/common.h b/src/common.h index 18ba72dd1..a4152caf2 100644 --- a/src/common.h +++ b/src/common.h @@ -47,6 +47,7 @@ # include # include "win32/msvc-compat.h" # include "win32/mingw-compat.h" +# include "win32/w32_common.h" # include "win32/win32-compat.h" # include "win32/error.h" # include "win32/version.h" @@ -76,6 +77,7 @@ #include "git2/types.h" #include "git2/errors.h" +#include "errors.h" #include "thread-utils.h" #include "integer.h" @@ -85,7 +87,7 @@ */ #include "git2/deprecated.h" -#include +#include "posix.h" #define DEFAULT_BUFSIZE 65536 #define FILEIO_BUFSIZE DEFAULT_BUFSIZE @@ -108,80 +110,6 @@ #define GIT_ERROR_CHECK_ERROR(code) \ do { int _err = (code); if (_err) return _err; } while (0) -/** - * Set the error message for this thread, formatting as needed. - */ - -void git_error_set(int error_class, const char *string, ...) GIT_FORMAT_PRINTF(2, 3); - -/** - * Set the error message for a regex failure, using the internal regex - * error code lookup and return a libgit error code. - */ -int git_error_set_regex(const regex_t *regex, int error_code); - -/** - * Set error message for user callback if needed. - * - * If the error code in non-zero and no error message is set, this - * sets a generic error message. - * - * @return This always returns the `error_code` parameter. - */ -GIT_INLINE(int) git_error_set_after_callback_function( - int error_code, const char *action) -{ - if (error_code) { - const git_error *e = git_error_last(); - if (!e || !e->message) - git_error_set(e ? e->klass : GIT_ERROR_CALLBACK, - "%s callback returned %d", action, error_code); - } - return error_code; -} - -#ifdef GIT_WIN32 -#define git_error_set_after_callback(code) \ - git_error_set_after_callback_function((code), __FUNCTION__) -#else -#define git_error_set_after_callback(code) \ - git_error_set_after_callback_function((code), __func__) -#endif - -/** - * Gets the system error code for this thread. - */ -int git_error_system_last(void); - -/** - * Sets the system error code for this thread. - */ -void git_error_system_set(int code); - -/** - * Structure to preserve libgit2 error state - */ -typedef struct { - int error_code; - unsigned int oom : 1; - git_error error_msg; -} git_error_state; - -/** - * Capture current error state to restore later, returning error code. - * If `error_code` is zero, this does not clear the current error state. - * You must either restore this error state, or free it. - */ -extern int git_error_state_capture(git_error_state *state, int error_code); - -/** - * Restore error state to a previous value, returning saved error code. - */ -extern int git_error_state_restore(git_error_state *state); - -/** Free an error state. */ -extern void git_error_state_free(git_error_state *state); - /** * Check a versioned structure for validity */ diff --git a/src/config.c b/src/config.c index fed0bfb29..8338da010 100644 --- a/src/config.c +++ b/src/config.c @@ -7,13 +7,15 @@ #include "config.h" -#include "sysdir.h" #include "git2/config.h" #include "git2/sys/config.h" -#include "vector.h" + #include "buf_text.h" #include "config_backend.h" +#include "regexp.h" +#include "sysdir.h" #include "transaction.h" +#include "vector.h" #if GIT_WIN32 # include #endif @@ -345,7 +347,7 @@ typedef struct { git_config_iterator parent; git_config_iterator *current; const git_config *cfg; - regex_t regex; + git_regexp regex; size_t i; } all_iter; @@ -423,7 +425,7 @@ static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_it */ while ((error = all_iter_next(entry, _iter)) == 0) { /* skip non-matching keys if regexp was provided */ - if (regexec(&iter->regex, (*entry)->name, 0, NULL, 0) != 0) + if (git_regexp_match(&iter->regex, (*entry)->name) != 0) continue; /* and simply return if we like the entry's name */ @@ -447,7 +449,7 @@ static void all_iter_glob_free(git_config_iterator *_iter) { all_iter *iter = (all_iter *) _iter; - regfree(&iter->regex); + git_regexp_dispose(&iter->regex); all_iter_free(_iter); } @@ -480,8 +482,7 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf iter = git__calloc(1, sizeof(all_iter)); GIT_ERROR_CHECK_ALLOC(iter); - if ((result = p_regcomp(&iter->regex, regexp, REG_EXTENDED)) != 0) { - git_error_set_regex(&iter->regex, result); + if ((result = git_regexp_compile(&iter->regex, regexp, 0)) < 0) { git__free(iter); return -1; } @@ -510,18 +511,13 @@ int git_config_backend_foreach_match( { git_config_entry *entry; git_config_iterator* iter; - regex_t regex; + git_regexp regex; int error = 0; assert(backend && cb); - if (regexp != NULL) { - if ((error = p_regcomp(®ex, regexp, REG_EXTENDED)) != 0) { - git_error_set_regex(®ex, error); - regfree(®ex); - return -1; - } - } + if (regexp && git_regexp_compile(®ex, regexp, 0) < 0) + return -1; if ((error = backend->iterator(&iter, backend)) < 0) { iter = NULL; @@ -530,7 +526,7 @@ int git_config_backend_foreach_match( while (!(iter->next(&entry, iter) < 0)) { /* skip non-matching keys if regexp was provided */ - if (regexp && regexec(®ex, entry->name, 0, NULL, 0) != 0) + if (regexp && git_regexp_match(®ex, entry->name) != 0) continue; /* abort iterator on non-zero return value */ @@ -541,7 +537,7 @@ int git_config_backend_foreach_match( } if (regexp != NULL) - regfree(®ex); + git_regexp_dispose(®ex); iter->free(iter); @@ -661,7 +657,7 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) error = backend->set(backend, name, value); if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL) - git_repository__cvar_cache_clear(GIT_REFCOUNT_OWNER(cfg)); + git_repository__configmap_lookup_cache_clear(GIT_REFCOUNT_OWNER(cfg)); return error; } @@ -777,7 +773,7 @@ int git_config_get_mapped( int *out, const git_config *cfg, const char *name, - const git_cvar_map *maps, + const git_configmap *maps, size_t map_n) { git_config_entry *entry; @@ -981,7 +977,7 @@ typedef struct { git_config_iterator parent; git_config_iterator *iter; char *name; - regex_t regex; + git_regexp regex; int have_regex; } multivar_iter; @@ -997,7 +993,7 @@ static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_it if (!iter->have_regex) return 0; - if (regexec(&iter->regex, (*entry)->value, 0, NULL, 0) == 0) + if (git_regexp_match(&iter->regex, (*entry)->value) == 0) return 0; } @@ -1012,7 +1008,7 @@ void multivar_iter_free(git_config_iterator *_iter) git__free(iter->name); if (iter->have_regex) - regfree(&iter->regex); + git_regexp_dispose(&iter->regex); git__free(iter); } @@ -1032,13 +1028,8 @@ int git_config_multivar_iterator_new(git_config_iterator **out, const git_config goto on_error; if (regexp != NULL) { - error = p_regcomp(&iter->regex, regexp, REG_EXTENDED); - if (error != 0) { - git_error_set_regex(&iter->regex, error); - error = -1; - regfree(&iter->regex); + if ((error = git_regexp_compile(&iter->regex, regexp, 0)) < 0) goto on_error; - } iter->have_regex = 1; } @@ -1230,7 +1221,7 @@ int git_config_unlock(git_config *cfg, int commit) int git_config_lookup_map_value( int *out, - const git_cvar_map *maps, + const git_configmap *maps, size_t map_n, const char *value) { @@ -1240,27 +1231,27 @@ int git_config_lookup_map_value( goto fail_parse; for (i = 0; i < map_n; ++i) { - const git_cvar_map *m = maps + i; + const git_configmap *m = maps + i; - switch (m->cvar_type) { - case GIT_CVAR_FALSE: - case GIT_CVAR_TRUE: { + switch (m->type) { + case GIT_CONFIGMAP_FALSE: + case GIT_CONFIGMAP_TRUE: { int bool_val; if (git__parse_bool(&bool_val, value) == 0 && - bool_val == (int)m->cvar_type) { + bool_val == (int)m->type) { *out = m->map_value; return 0; } break; } - case GIT_CVAR_INT32: + case GIT_CONFIGMAP_INT32: if (git_config_parse_int32(out, value) == 0) return 0; break; - case GIT_CVAR_STRING: + case GIT_CONFIGMAP_STRING: if (strcasecmp(value, m->str_match) == 0) { *out = m->map_value; return 0; @@ -1274,18 +1265,18 @@ fail_parse: return -1; } -int git_config_lookup_map_enum(git_cvar_t *type_out, const char **str_out, - const git_cvar_map *maps, size_t map_n, int enum_val) +int git_config_lookup_map_enum(git_configmap_t *type_out, const char **str_out, + const git_configmap *maps, size_t map_n, int enum_val) { size_t i; for (i = 0; i < map_n; i++) { - const git_cvar_map *m = &maps[i]; + const git_configmap *m = &maps[i]; if (m->map_value != enum_val) continue; - *type_out = m->cvar_type; + *type_out = m->type; *str_out = m->str_match; return 0; } diff --git a/src/config.h b/src/config.h index f5855113f..a1d8f7d23 100644 --- a/src/config.h +++ b/src/config.h @@ -66,18 +66,19 @@ extern int git_config__get_bool_force( extern int git_config__get_int_force( const git_config *cfg, const char *key, int fallback_value); -/* API for repository cvar-style lookups from config - not cached, but - * uses cvar value maps and fallbacks +/* API for repository configmap-style lookups from config - not cached, but + * uses configmap value maps and fallbacks */ -extern int git_config__cvar( - int *out, git_config *config, git_cvar_cached cvar); +extern int git_config__configmap_lookup( + int *out, git_config *config, git_configmap_item item); /** * The opposite of git_config_lookup_map_value, we take an enum value * and map it to the string or bool value on the config. */ -int git_config_lookup_map_enum(git_cvar_t *type_out, const char **str_out, - const git_cvar_map *maps, size_t map_n, int enum_val); +int git_config_lookup_map_enum(git_configmap_t *type_out, + const char **str_out, const git_configmap *maps, + size_t map_n, int enum_val); /** * Unlock the backend with the highest priority diff --git a/src/config_backend.h b/src/config_backend.h index 6d678ae43..dbb190514 100644 --- a/src/config_backend.h +++ b/src/config_backend.h @@ -25,6 +25,18 @@ */ extern int git_config_backend_from_file(git_config_backend **out, const char *path); +/** + * Create a readonly configuration file backend from another backend + * + * This copies the complete contents of the source backend to the + * new backend. The new backend will be completely read-only and + * cannot be modified. + * + * @param out the new snapshotted backend + * @param source the backend to copy + */ +extern int git_config_backend_snapshot(git_config_backend **out, git_config_backend *source); + /** * Create an in-memory configuration file backend * diff --git a/src/config_cache.c b/src/config_cache.c index 2530217c9..e4e071b54 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -7,7 +7,7 @@ #include "common.h" -#include "fileops.h" +#include "futils.h" #include "repository.h" #include "config.h" #include "git2/config.h" @@ -15,8 +15,8 @@ #include "filter.h" struct map_data { - const char *cvar_name; - git_cvar_map *maps; + const char *name; + git_configmap *maps; size_t map_count; int default_value; }; @@ -29,11 +29,11 @@ struct map_data { * value is native. See gitattributes(5) for more information on * end-of-line conversion. */ -static git_cvar_map _cvar_map_eol[] = { - {GIT_CVAR_FALSE, NULL, GIT_EOL_UNSET}, - {GIT_CVAR_STRING, "lf", GIT_EOL_LF}, - {GIT_CVAR_STRING, "crlf", GIT_EOL_CRLF}, - {GIT_CVAR_STRING, "native", GIT_EOL_NATIVE} +static git_configmap _configmap_eol[] = { + {GIT_CONFIGMAP_FALSE, NULL, GIT_EOL_UNSET}, + {GIT_CONFIGMAP_STRING, "lf", GIT_EOL_LF}, + {GIT_CONFIGMAP_STRING, "crlf", GIT_EOL_CRLF}, + {GIT_CONFIGMAP_STRING, "native", GIT_EOL_NATIVE} }; /* @@ -46,55 +46,55 @@ static git_cvar_map _cvar_map_eol[] = { * does not have normalized line endings. This variable can be set to input, * in which case no output conversion is performed. */ -static git_cvar_map _cvar_map_autocrlf[] = { - {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, - {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, - {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT} +static git_configmap _configmap_autocrlf[] = { + {GIT_CONFIGMAP_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, + {GIT_CONFIGMAP_STRING, "input", GIT_AUTO_CRLF_INPUT} }; -static git_cvar_map _cvar_map_safecrlf[] = { - {GIT_CVAR_FALSE, NULL, GIT_SAFE_CRLF_FALSE}, - {GIT_CVAR_TRUE, NULL, GIT_SAFE_CRLF_FAIL}, - {GIT_CVAR_STRING, "warn", GIT_SAFE_CRLF_WARN} +static git_configmap _configmap_safecrlf[] = { + {GIT_CONFIGMAP_FALSE, NULL, GIT_SAFE_CRLF_FALSE}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_SAFE_CRLF_FAIL}, + {GIT_CONFIGMAP_STRING, "warn", GIT_SAFE_CRLF_WARN} }; -static git_cvar_map _cvar_map_logallrefupdates[] = { - {GIT_CVAR_FALSE, NULL, GIT_LOGALLREFUPDATES_FALSE}, - {GIT_CVAR_TRUE, NULL, GIT_LOGALLREFUPDATES_TRUE}, - {GIT_CVAR_STRING, "always", GIT_LOGALLREFUPDATES_ALWAYS}, +static git_configmap _configmap_logallrefupdates[] = { + {GIT_CONFIGMAP_FALSE, NULL, GIT_LOGALLREFUPDATES_FALSE}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_LOGALLREFUPDATES_TRUE}, + {GIT_CONFIGMAP_STRING, "always", GIT_LOGALLREFUPDATES_ALWAYS}, }; /* * Generic map for integer values */ -static git_cvar_map _cvar_map_int[] = { - {GIT_CVAR_INT32, NULL, 0}, +static git_configmap _configmap_int[] = { + {GIT_CONFIGMAP_INT32, NULL, 0}, }; -static struct map_data _cvar_maps[] = { - {"core.autocrlf", _cvar_map_autocrlf, ARRAY_SIZE(_cvar_map_autocrlf), GIT_AUTO_CRLF_DEFAULT}, - {"core.eol", _cvar_map_eol, ARRAY_SIZE(_cvar_map_eol), GIT_EOL_DEFAULT}, +static struct map_data _configmaps[] = { + {"core.autocrlf", _configmap_autocrlf, ARRAY_SIZE(_configmap_autocrlf), GIT_AUTO_CRLF_DEFAULT}, + {"core.eol", _configmap_eol, ARRAY_SIZE(_configmap_eol), GIT_EOL_DEFAULT}, {"core.symlinks", NULL, 0, GIT_SYMLINKS_DEFAULT }, {"core.ignorecase", NULL, 0, GIT_IGNORECASE_DEFAULT }, {"core.filemode", NULL, 0, GIT_FILEMODE_DEFAULT }, {"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT }, {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT }, - {"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT }, + {"core.abbrev", _configmap_int, 1, GIT_ABBREV_DEFAULT }, {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, - {"core.safecrlf", _cvar_map_safecrlf, ARRAY_SIZE(_cvar_map_safecrlf), GIT_SAFE_CRLF_DEFAULT}, - {"core.logallrefupdates", _cvar_map_logallrefupdates, ARRAY_SIZE(_cvar_map_logallrefupdates), GIT_LOGALLREFUPDATES_DEFAULT}, + {"core.safecrlf", _configmap_safecrlf, ARRAY_SIZE(_configmap_safecrlf), GIT_SAFE_CRLF_DEFAULT}, + {"core.logallrefupdates", _configmap_logallrefupdates, ARRAY_SIZE(_configmap_logallrefupdates), GIT_LOGALLREFUPDATES_DEFAULT}, {"core.protecthfs", NULL, 0, GIT_PROTECTHFS_DEFAULT }, {"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT }, {"core.fsyncobjectfiles", NULL, 0, GIT_FSYNCOBJECTFILES_DEFAULT }, }; -int git_config__cvar(int *out, git_config *config, git_cvar_cached cvar) +int git_config__configmap_lookup(int *out, git_config *config, git_configmap_item item) { int error = 0; - struct map_data *data = &_cvar_maps[(int)cvar]; + struct map_data *data = &_configmaps[(int)item]; git_config_entry *entry; - if ((error = git_config__lookup_entry(&entry, config, data->cvar_name, false)) < 0) + if ((error = git_config__lookup_entry(&entry, config, data->name, false)) < 0) return error; if (!entry) @@ -109,29 +109,29 @@ int git_config__cvar(int *out, git_config *config, git_cvar_cached cvar) return error; } -int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar) +int git_repository__configmap_lookup(int *out, git_repository *repo, git_configmap_item item) { - *out = repo->cvar_cache[(int)cvar]; + *out = repo->configmap_cache[(int)item]; - if (*out == GIT_CVAR_NOT_CACHED) { + if (*out == GIT_CONFIGMAP_NOT_CACHED) { int error; git_config *config; if ((error = git_repository_config__weakptr(&config, repo)) < 0 || - (error = git_config__cvar(out, config, cvar)) < 0) + (error = git_config__configmap_lookup(out, config, item)) < 0) return error; - repo->cvar_cache[(int)cvar] = *out; + repo->configmap_cache[(int)item] = *out; } return 0; } -void git_repository__cvar_cache_clear(git_repository *repo) +void git_repository__configmap_lookup_cache_clear(git_repository *repo) { int i; - for (i = 0; i < GIT_CVAR_CACHE_MAX; ++i) - repo->cvar_cache[i] = GIT_CVAR_NOT_CACHED; + for (i = 0; i < GIT_CONFIGMAP_CACHE_MAX; ++i) + repo->configmap_cache[i] = GIT_CONFIGMAP_NOT_CACHED; } diff --git a/src/config_entries.c b/src/config_entries.c index f6277bcc7..3ddd3369a 100644 --- a/src/config_entries.c +++ b/src/config_entries.c @@ -11,6 +11,7 @@ typedef struct config_entry_list { struct config_entry_list *next; struct config_entry_list *last; git_config_entry *entry; + bool first; } config_entry_list; typedef struct config_entries_iterator { @@ -25,31 +26,6 @@ struct git_config_entries { config_entry_list *list; }; -static void config_entry_list_free(config_entry_list *list) -{ - config_entry_list *next; - - while (list != NULL) { - next = list->next; - - git__free((char*) list->entry->name); - git__free((char *) list->entry->value); - git__free(list->entry); - git__free(list); - - list = next; - }; -} - -static void config_entry_list_append(config_entry_list **list, config_entry_list *entry) -{ - if (*list) - (*list)->last->next = entry; - else - *list = entry; - (*list)->last = entry; -} - int git_config_entries_new(git_config_entries **out) { git_config_entries *entries; @@ -59,7 +35,7 @@ int git_config_entries_new(git_config_entries **out) GIT_ERROR_CHECK_ALLOC(entries); GIT_REFCOUNT_INC(entries); - if ((error = git_strmap_alloc(&entries->map)) < 0) + if ((error = git_strmap_new(&entries->map)) < 0) git__free(entries); else *out = entries; @@ -67,6 +43,36 @@ int git_config_entries_new(git_config_entries **out) return error; } +int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry) +{ + git_config_entry *duplicated; + int error; + + duplicated = git__calloc(1, sizeof(git_config_entry)); + GIT_ERROR_CHECK_ALLOC(duplicated); + + duplicated->name = git__strdup(entry->name); + GIT_ERROR_CHECK_ALLOC(duplicated->name); + + if (entry->value) { + duplicated->value = git__strdup(entry->value); + GIT_ERROR_CHECK_ALLOC(duplicated->value); + } + duplicated->level = entry->level; + duplicated->include_depth = entry->include_depth; + + if ((error = git_config_entries_append(entries, duplicated)) < 0) + goto out; + +out: + if (error && duplicated) { + git__free((char *) duplicated->name); + git__free((char *) duplicated->value); + git__free(duplicated); + } + return error; +} + int git_config_entries_dup(git_config_entries **out, git_config_entries *entries) { git_config_entries *result = NULL; @@ -76,22 +82,9 @@ int git_config_entries_dup(git_config_entries **out, git_config_entries *entries if ((error = git_config_entries_new(&result)) < 0) goto out; - for (head = entries->list; head; head = head->next) { - git_config_entry *dup; - - dup = git__calloc(1, sizeof(git_config_entry)); - dup->name = git__strdup(head->entry->name); - GIT_ERROR_CHECK_ALLOC(dup->name); - if (head->entry->value) { - dup->value = git__strdup(head->entry->value); - GIT_ERROR_CHECK_ALLOC(dup->value); - } - dup->level = head->entry->level; - dup->include_depth = head->entry->include_depth; - - if ((error = git_config_entries_append(result, dup)) < 0) + for (head = entries->list; head; head = head->next) + if ((git_config_entries_dup_entry(result, head->entry)) < 0) goto out; - } *out = result; result = NULL; @@ -110,12 +103,15 @@ static void config_entries_free(git_config_entries *entries) { config_entry_list *list = NULL, *next; - git_strmap_foreach_value(entries->map, list, config_entry_list_free(list)); git_strmap_free(entries->map); list = entries->list; while (list != NULL) { next = list->next; + if (list->first) + git__free((char *) list->entry->name); + git__free((char *) list->entry->value); + git__free(list->entry); git__free(list); list = next; } @@ -131,55 +127,33 @@ void git_config_entries_free(git_config_entries *entries) int git_config_entries_append(git_config_entries *entries, git_config_entry *entry) { - config_entry_list *existing, *var; - int error = 0; - size_t pos; + config_entry_list *existing, *head; - var = git__calloc(1, sizeof(config_entry_list)); - GIT_ERROR_CHECK_ALLOC(var); - var->entry = entry; + head = git__calloc(1, sizeof(config_entry_list)); + GIT_ERROR_CHECK_ALLOC(head); + head->entry = entry; - pos = git_strmap_lookup_index(entries->map, entry->name); - if (!git_strmap_valid_index(entries->map, pos)) { - /* - * We only ever inspect `last` from the first config - * entry in a multivar. In case where this new entry is - * the first one in the entry map, it will also be the - * last one at the time of adding it, which is - * why we set `last` here to itself. Otherwise we - * do not have to set `last` and leave it set to - * `NULL`. - */ - var->last = var; - - git_strmap_insert(entries->map, entry->name, var, &error); - - if (error > 0) - error = 0; + /* + * This is a micro-optimization for configuration files + * with a lot of same keys. As for multivars the entry's + * key will be the same for all entries, we can just free + * all except the first entry's name and just re-use it. + */ + if ((existing = git_strmap_get(entries->map, entry->name)) != NULL) { + git__free((char *) entry->name); + entry->name = existing->entry->name; } else { - existing = git_strmap_value_at(entries->map, pos); - config_entry_list_append(&existing, var); + head->first = 1; } - var = git__calloc(1, sizeof(config_entry_list)); - GIT_ERROR_CHECK_ALLOC(var); - var->entry = entry; - config_entry_list_append(&entries->list, var); + if (entries->list) + entries->list->last->next = head; + else + entries->list = head; + entries->list->last = head; - return error; -} - -int config_entry_get(config_entry_list **out, git_config_entries *entries, const char *key) -{ - size_t pos; - - pos = git_strmap_lookup_index(entries->map, key); - - /* no error message; the config system will write one */ - if (!git_strmap_valid_index(entries->map, pos)) - return GIT_ENOTFOUND; - - *out = git_strmap_value_at(entries->map, pos); + if (git_strmap_set(entries->map, entry->name, head) < 0) + return -1; return 0; } @@ -187,24 +161,20 @@ int config_entry_get(config_entry_list **out, git_config_entries *entries, const int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key) { config_entry_list *entry; - int error; - - if ((error = config_entry_get(&entry, entries, key)) < 0) - return error; - *out = entry->last->entry; - + if ((entry = git_strmap_get(entries->map, key)) == NULL) + return GIT_ENOTFOUND; + *out = entry->entry; return 0; } int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key) { config_entry_list *entry; - int error; - if ((error = config_entry_get(&entry, entries, key)) < 0) - return error; + if ((entry = git_strmap_get(entries->map, key)) == NULL) + return GIT_ENOTFOUND; - if (entry->next != NULL) { + if (!entry->first) { git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar"); return -1; } @@ -219,14 +189,14 @@ int git_config_entries_get_unique(git_config_entry **out, git_config_entries *en return 0; } -void config_iterator_free(git_config_iterator *iter) +static void config_iterator_free(git_config_iterator *iter) { config_entries_iterator *it = (config_entries_iterator *) iter; git_config_entries_free(it->entries); git__free(it); } -int config_iterator_next( +static int config_iterator_next( git_config_entry **entry, git_config_iterator *iter) { diff --git a/src/config_entries.h b/src/config_entries.h index 6fdbc41ba..832379e74 100644 --- a/src/config_entries.h +++ b/src/config_entries.h @@ -14,6 +14,7 @@ typedef struct git_config_entries git_config_entries; int git_config_entries_new(git_config_entries **out); int git_config_entries_dup(git_config_entries **out, git_config_entries *entries); +int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry); void git_config_entries_incref(git_config_entries *entries); void git_config_entries_free(git_config_entries *entries); /* Add or append the new config option */ diff --git a/src/config_file.c b/src/config_file.c index b3a3bda46..b1e002836 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -7,36 +7,35 @@ #include "config.h" -#include "filebuf.h" -#include "sysdir.h" -#include "buffer.h" -#include "buf_text.h" #include "git2/config.h" #include "git2/sys/config.h" -#include "git2/types.h" -#include "strmap.h" -#include "array.h" -#include "config_parse.h" -#include "config_entries.h" -#include -#include -#include +#include "array.h" +#include "buffer.h" +#include "config_backend.h" +#include "config_entries.h" +#include "config_parse.h" +#include "filebuf.h" +#include "regexp.h" +#include "sysdir.h" +#include "wildmatch.h" /* Max depth for [include] directives */ #define MAX_INCLUDE_DEPTH 10 +typedef struct config_file { + git_futils_filestamp stamp; + git_oid checksum; + char *path; + git_array_t(struct config_file) includes; +} config_file; + typedef struct { git_config_backend parent; - /* mutex to coordinate accessing the values */ git_mutex values_mutex; git_config_entries *entries; const git_repository *repo; git_config_level_t level; -} diskfile_header; - -typedef struct { - diskfile_header header; git_array_t(git_config_parser) readers; @@ -44,60 +43,47 @@ typedef struct { git_filebuf locked_buf; git_buf locked_content; - struct config_file file; -} diskfile_backend; - -typedef struct { - diskfile_header header; - - diskfile_backend *snapshot_from; -} diskfile_readonly_backend; + config_file file; +} config_file_backend; typedef struct { const git_repository *repo; - const char *file_path; + config_file *file; git_config_entries *entries; git_config_level_t level; unsigned int depth; -} diskfile_parse_state; +} config_file_parse_data; -static int config_read(git_config_entries *entries, const git_repository *repo, git_config_file *file, git_config_level_t level, int depth); -static int config_write(diskfile_backend *cfg, const char *orig_key, const char *key, const regex_t *preg, const char *value); +static int config_file_read(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth); +static int config_file_read_buffer(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth, const char *buf, size_t buflen); +static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value); static char *escape_value(const char *ptr); -static int config_snapshot(git_config_backend **out, git_config_backend *in); - -static int config_error_readonly(void) -{ - git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); - return -1; -} - /** * Take the current values map from the backend and increase its * refcount. This is its own function to make sure we use the mutex to * avoid the map pointer from changing under us. */ -static git_config_entries *diskfile_entries_take(diskfile_header *h) +static int config_file_entries_take(git_config_entries **out, config_file_backend *b) { - git_config_entries *entries; + int error; - if (git_mutex_lock(&h->values_mutex) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock config backend"); - return NULL; + if ((error = git_mutex_lock(&b->values_mutex)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock config backend"); + return error; } - entries = h->entries; - git_config_entries_incref(entries); + git_config_entries_incref(b->entries); + *out = b->entries; - git_mutex_unlock(&h->values_mutex); + git_mutex_unlock(&b->values_mutex); - return entries; + return 0; } -static void config_file_clear(struct config_file *file) +static void config_file_clear(config_file *file) { - struct config_file *include; + config_file *include; uint32_t i; if (file == NULL) @@ -111,31 +97,40 @@ static void config_file_clear(struct config_file *file) git__free(file->path); } -static int config_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) +static int config_file_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) { + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); int res; - diskfile_backend *b = (diskfile_backend *)cfg; - b->header.level = level; - b->header.repo = repo; + b->level = level; + b->repo = repo; - if ((res = git_config_entries_new(&b->header.entries)) < 0) + if ((res = git_config_entries_new(&b->entries)) < 0) return res; if (!git_path_exists(b->file.path)) return 0; - if (res < 0 || (res = config_read(b->header.entries, repo, &b->file, level, 0)) < 0) { - git_config_entries_free(b->header.entries); - b->header.entries = NULL; + /* + * git silently ignores configuration files that are not + * readable. We emulate that behavior. This is particularly + * important for sandboxed applications on macOS where the + * git configuration files may not be readable. + */ + if (p_access(b->file.path, R_OK) < 0) + return GIT_ENOTFOUND; + + if (res < 0 || (res = config_file_read(b->entries, repo, &b->file, level, 0)) < 0) { + git_config_entries_free(b->entries); + b->entries = NULL; } return res; } -static int config_is_modified(int *modified, struct config_file *file) +static int config_file_is_modified(int *modified, config_file *file) { - git_config_file *include; + config_file *include; git_buf buf = GIT_BUF_INIT; git_oid hash; uint32_t i; @@ -143,6 +138,9 @@ static int config_is_modified(int *modified, struct config_file *file) *modified = 0; + if (!git_futils_filestamp_check(&file->stamp, file->path)) + goto check_includes; + if ((error = git_futils_readbuffer(&buf, file->path)) < 0) goto out; @@ -154,8 +152,9 @@ static int config_is_modified(int *modified, struct config_file *file) goto out; } +check_includes: git_array_foreach(file->includes, i, include) { - if ((error = config_is_modified(modified, include)) < 0 || *modified) + if ((error = config_file_is_modified(modified, include)) < 0 || *modified) goto out; } @@ -165,89 +164,125 @@ out: return error; } -static int config_refresh(git_config_backend *cfg) +static int config_file_set_entries(git_config_backend *cfg, git_config_entries *entries) { - diskfile_backend *b = (diskfile_backend *)cfg; - git_config_entries *entries = NULL, *tmp; - git_config_file *include; - int error, modified; + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *old = NULL; + config_file *include; + int error; uint32_t i; - if (b->header.parent.readonly) - return config_error_readonly(); + if (b->parent.readonly) { + git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); + return -1; + } - error = config_is_modified(&modified, &b->file); - if (error < 0 && error != GIT_ENOTFOUND) + git_array_foreach(b->file.includes, i, include) + config_file_clear(include); + git_array_clear(b->file.includes); + + if ((error = git_mutex_lock(&b->values_mutex)) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock config backend"); + goto out; + } + + old = b->entries; + b->entries = entries; + + git_mutex_unlock(&b->values_mutex); + +out: + git_config_entries_free(old); + return error; +} + +static int config_file_refresh_from_buffer(git_config_backend *cfg, const char *buf, size_t buflen) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *entries = NULL; + int error; + + if ((error = git_config_entries_new(&entries)) < 0 || + (error = config_file_read_buffer(entries, b->repo, &b->file, + b->level, 0, buf, buflen)) < 0 || + (error = config_file_set_entries(cfg, entries)) < 0) + goto out; + + entries = NULL; +out: + git_config_entries_free(entries); + return error; +} + +static int config_file_refresh(git_config_backend *cfg) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_config_entries *entries = NULL; + int error, modified; + + if (cfg->readonly) + return 0; + + if ((error = config_file_is_modified(&modified, &b->file)) < 0 && error != GIT_ENOTFOUND) goto out; if (!modified) return 0; - if ((error = git_config_entries_new(&entries)) < 0) + if ((error = git_config_entries_new(&entries)) < 0 || + (error = config_file_read(entries, b->repo, &b->file, b->level, 0)) < 0 || + (error = config_file_set_entries(cfg, entries)) < 0) goto out; - /* Reparse the current configuration */ - git_array_foreach(b->file.includes, i, include) { - config_file_clear(include); - } - git_array_clear(b->file.includes); - - if ((error = config_read(entries, b->header.repo, &b->file, b->header.level, 0)) < 0) - goto out; - - if ((error = git_mutex_lock(&b->header.values_mutex)) < 0) { - git_error_set(GIT_ERROR_OS, "failed to lock config backend"); - goto out; - } - - tmp = b->header.entries; - b->header.entries = entries; - entries = tmp; - - git_mutex_unlock(&b->header.values_mutex); - + entries = NULL; out: git_config_entries_free(entries); return (error == GIT_ENOTFOUND) ? 0 : error; } -static void backend_free(git_config_backend *_backend) +static void config_file_free(git_config_backend *_backend) { - diskfile_backend *backend = (diskfile_backend *)_backend; + config_file_backend *backend = GIT_CONTAINER_OF(_backend, config_file_backend, parent); if (backend == NULL) return; config_file_clear(&backend->file); - git_config_entries_free(backend->header.entries); - git_mutex_free(&backend->header.values_mutex); + git_config_entries_free(backend->entries); + git_mutex_free(&backend->values_mutex); git__free(backend); } -static int config_iterator_new( +static int config_file_iterator( git_config_iterator **iter, - struct git_config_backend* backend) + struct git_config_backend *backend) { - diskfile_header *bh = (diskfile_header *) backend; - git_config_entries *entries; + config_file_backend *b = GIT_CONTAINER_OF(backend, config_file_backend, parent); + git_config_entries *dupped = NULL, *entries = NULL; int error; - if ((error = git_config_entries_dup(&entries, bh->entries)) < 0) - return error; - - if ((error = git_config_entries_iterator_new(iter, entries)) < 0) + if ((error = config_file_refresh(backend)) < 0 || + (error = config_file_entries_take(&entries, b)) < 0 || + (error = git_config_entries_dup(&dupped, entries)) < 0 || + (error = git_config_entries_iterator_new(iter, dupped)) < 0) goto out; out: /* Let iterator delete duplicated entries when it's done */ git_config_entries_free(entries); + git_config_entries_free(dupped); return error; } -static int config_set(git_config_backend *cfg, const char *name, const char *value) +static int config_file_snapshot(git_config_backend **out, git_config_backend *backend) { - diskfile_backend *b = (diskfile_backend *)cfg; + return git_config_backend_snapshot(out, backend); +} + +static int config_file_set(git_config_backend *cfg, const char *name, const char *value) +{ + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); git_config_entries *entries; git_config_entry *existing; char *key, *esc_value = NULL; @@ -256,8 +291,8 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val if ((error = git_config__normalize_name(name, &key)) < 0) return error; - if ((entries = diskfile_entries_take(&b->header)) == NULL) - return -1; + if ((error = config_file_entries_take(&entries, b)) < 0) + return error; /* Check whether we'd be modifying an included or multivar key */ if ((error = git_config_entries_get_unique(&existing, entries, key)) < 0) { @@ -277,11 +312,9 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val GIT_ERROR_CHECK_ALLOC(esc_value); } - if ((error = config_write(b, name, key, NULL, esc_value)) < 0) + if ((error = config_file_write(b, name, key, NULL, esc_value)) < 0) goto out; - error = config_refresh(cfg); - out: git_config_entries_free(entries); git__free(esc_value); @@ -290,7 +323,7 @@ out: } /* release the map containing the entry as an equivalent to freeing it */ -static void free_diskfile_entry(git_config_entry *entry) +static void config_file_entry_free(git_config_entry *entry) { git_config_entries *entries = (git_config_entries *) entry->payload; git_config_entries_free(entries); @@ -299,67 +332,61 @@ static void free_diskfile_entry(git_config_entry *entry) /* * Internal function that actually gets the value in string form */ -static int config_get(git_config_backend *cfg, const char *key, git_config_entry **out) +static int config_file_get(git_config_backend *cfg, const char *key, git_config_entry **out) { - diskfile_header *h = (diskfile_header *)cfg; + config_file_backend *h = GIT_CONTAINER_OF(cfg, config_file_backend, parent); git_config_entries *entries = NULL; git_config_entry *entry; int error = 0; - if (!h->parent.readonly && ((error = config_refresh(cfg)) < 0)) + if (!h->parent.readonly && ((error = config_file_refresh(cfg)) < 0)) return error; - if ((entries = diskfile_entries_take(h)) == NULL) - return -1; + if ((error = config_file_entries_take(&entries, h)) < 0) + return error; if ((error = (git_config_entries_get(&entry, entries, key))) < 0) { git_config_entries_free(entries); return error; } - entry->free = free_diskfile_entry; + entry->free = config_file_entry_free; entry->payload = entries; *out = entry; return 0; } -static int config_set_multivar( +static int config_file_set_multivar( git_config_backend *cfg, const char *name, const char *regexp, const char *value) { - diskfile_backend *b = (diskfile_backend *)cfg; - char *key; - regex_t preg; + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); + git_regexp preg; int result; + char *key; assert(regexp); if ((result = git_config__normalize_name(name, &key)) < 0) return result; - result = p_regcomp(&preg, regexp, REG_EXTENDED); - if (result != 0) { - git_error_set_regex(&preg, result); - result = -1; - goto out; - } - - /* If we do have it, set call config_write() and reload */ - if ((result = config_write(b, name, key, &preg, value)) < 0) + if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) goto out; - result = config_refresh(cfg); + /* If we do have it, set call config_file_write() and reload */ + if ((result = config_file_write(b, name, key, &preg, value)) < 0) + goto out; out: git__free(key); - regfree(&preg); + git_regexp_dispose(&preg); return result; } -static int config_delete(git_config_backend *cfg, const char *name) +static int config_file_delete(git_config_backend *cfg, const char *name) { - diskfile_backend *b = (diskfile_backend *)cfg; + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); git_config_entries *entries = NULL; git_config_entry *entry; char *key = NULL; @@ -368,7 +395,7 @@ static int config_delete(git_config_backend *cfg, const char *name) if ((error = git_config__normalize_name(name, &key)) < 0) goto out; - if ((entries = diskfile_entries_take(&b->header)) == NULL) + if ((error = config_file_entries_take(&entries, b)) < 0) goto out; /* Check whether we'd be modifying an included or multivar key */ @@ -378,10 +405,7 @@ static int config_delete(git_config_backend *cfg, const char *name) goto out; } - if ((error = config_write(b, name, entry->name, NULL, NULL)) < 0) - goto out; - - if ((error = config_refresh(cfg)) < 0) + if ((error = config_file_write(b, name, entry->name, NULL, NULL)) < 0) goto out; out: @@ -390,22 +414,20 @@ out: return error; } -static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) +static int config_file_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) { - diskfile_backend *b = (diskfile_backend *)cfg; + config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent); git_config_entries *entries = NULL; git_config_entry *entry = NULL; - regex_t preg = { 0 }; + git_regexp preg = GIT_REGEX_INIT; char *key = NULL; int result; if ((result = git_config__normalize_name(name, &key)) < 0) goto out; - if ((entries = diskfile_entries_take(&b->header)) == NULL) { - result = -1; + if ((result = config_file_entries_take(&entries, b)) < 0) goto out; - } if ((result = git_config_entries_get(&entry, entries, key)) < 0) { if (result == GIT_ENOTFOUND) @@ -413,28 +435,22 @@ static int config_delete_multivar(git_config_backend *cfg, const char *name, con goto out; } - if ((result = p_regcomp(&preg, regexp, REG_EXTENDED)) != 0) { - git_error_set_regex(&preg, result); - result = -1; - goto out; - } - - if ((result = config_write(b, name, key, &preg, NULL)) < 0) + if ((result = git_regexp_compile(&preg, regexp, 0)) < 0) goto out; - if ((result = config_refresh(cfg)) < 0) + if ((result = config_file_write(b, name, key, &preg, NULL)) < 0) goto out; out: git_config_entries_free(entries); git__free(key); - regfree(&preg); + git_regexp_dispose(&preg); return result; } -static int config_lock(git_config_backend *_cfg) +static int config_file_lock(git_config_backend *_cfg) { - diskfile_backend *cfg = (diskfile_backend *) _cfg; + config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent); int error; if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file.path, 0, GIT_CONFIG_FILE_MODE)) < 0) @@ -451,9 +467,9 @@ static int config_lock(git_config_backend *_cfg) } -static int config_unlock(git_config_backend *_cfg, int success) +static int config_file_unlock(git_config_backend *_cfg, int success) { - diskfile_backend *cfg = (diskfile_backend *) _cfg; + config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent); int error = 0; if (success) { @@ -470,145 +486,29 @@ static int config_unlock(git_config_backend *_cfg, int success) int git_config_backend_from_file(git_config_backend **out, const char *path) { - diskfile_backend *backend; + config_file_backend *backend; - backend = git__calloc(1, sizeof(diskfile_backend)); + backend = git__calloc(1, sizeof(config_file_backend)); GIT_ERROR_CHECK_ALLOC(backend); - backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; - git_mutex_init(&backend->header.values_mutex); + backend->parent.version = GIT_CONFIG_BACKEND_VERSION; + git_mutex_init(&backend->values_mutex); backend->file.path = git__strdup(path); GIT_ERROR_CHECK_ALLOC(backend->file.path); git_array_init(backend->file.includes); - backend->header.parent.open = config_open; - backend->header.parent.get = config_get; - backend->header.parent.set = config_set; - backend->header.parent.set_multivar = config_set_multivar; - backend->header.parent.del = config_delete; - backend->header.parent.del_multivar = config_delete_multivar; - backend->header.parent.iterator = config_iterator_new; - backend->header.parent.snapshot = config_snapshot; - backend->header.parent.lock = config_lock; - backend->header.parent.unlock = config_unlock; - backend->header.parent.free = backend_free; - - *out = (git_config_backend *)backend; - - return 0; -} - -static int config_set_readonly(git_config_backend *cfg, const char *name, const char *value) -{ - GIT_UNUSED(cfg); - GIT_UNUSED(name); - GIT_UNUSED(value); - - return config_error_readonly(); -} - -static int config_set_multivar_readonly( - git_config_backend *cfg, const char *name, const char *regexp, const char *value) -{ - GIT_UNUSED(cfg); - GIT_UNUSED(name); - GIT_UNUSED(regexp); - GIT_UNUSED(value); - - return config_error_readonly(); -} - -static int config_delete_multivar_readonly(git_config_backend *cfg, const char *name, const char *regexp) -{ - GIT_UNUSED(cfg); - GIT_UNUSED(name); - GIT_UNUSED(regexp); - - return config_error_readonly(); -} - -static int config_delete_readonly(git_config_backend *cfg, const char *name) -{ - GIT_UNUSED(cfg); - GIT_UNUSED(name); - - return config_error_readonly(); -} - -static int config_lock_readonly(git_config_backend *_cfg) -{ - GIT_UNUSED(_cfg); - - return config_error_readonly(); -} - -static int config_unlock_readonly(git_config_backend *_cfg, int success) -{ - GIT_UNUSED(_cfg); - GIT_UNUSED(success); - - return config_error_readonly(); -} - -static void backend_readonly_free(git_config_backend *_backend) -{ - diskfile_backend *backend = (diskfile_backend *)_backend; - - if (backend == NULL) - return; - - git_config_entries_free(backend->header.entries); - git_mutex_free(&backend->header.values_mutex); - git__free(backend); -} - -static int config_readonly_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) -{ - diskfile_readonly_backend *b = (diskfile_readonly_backend *) cfg; - diskfile_backend *src = b->snapshot_from; - diskfile_header *src_header = &src->header; - git_config_entries *entries; - int error; - - if (!src_header->parent.readonly && (error = config_refresh(&src_header->parent)) < 0) - return error; - - /* We're just copying data, don't care about the level or repo*/ - GIT_UNUSED(level); - GIT_UNUSED(repo); - - if ((entries = diskfile_entries_take(src_header)) == NULL) - return -1; - b->header.entries = entries; - - return 0; -} - -static int config_snapshot(git_config_backend **out, git_config_backend *in) -{ - diskfile_readonly_backend *backend; - - backend = git__calloc(1, sizeof(diskfile_readonly_backend)); - GIT_ERROR_CHECK_ALLOC(backend); - - backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; - git_mutex_init(&backend->header.values_mutex); - - backend->snapshot_from = (diskfile_backend *) in; - - backend->header.parent.readonly = 1; - backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; - backend->header.parent.open = config_readonly_open; - backend->header.parent.get = config_get; - backend->header.parent.set = config_set_readonly; - backend->header.parent.set_multivar = config_set_multivar_readonly; - backend->header.parent.del = config_delete_readonly; - backend->header.parent.del_multivar = config_delete_multivar_readonly; - backend->header.parent.iterator = config_iterator_new; - backend->header.parent.lock = config_lock_readonly; - backend->header.parent.unlock = config_unlock_readonly; - backend->header.parent.free = backend_readonly_free; + backend->parent.open = config_file_open; + backend->parent.get = config_file_get; + backend->parent.set = config_file_set; + backend->parent.set_multivar = config_file_set_multivar; + backend->parent.del = config_file_delete; + backend->parent.del_multivar = config_file_delete_multivar; + backend->parent.iterator = config_file_iterator; + backend->parent.snapshot = config_file_snapshot; + backend->parent.lock = config_file_lock; + backend->parent.unlock = config_file_unlock; + backend->parent.free = config_file_free; *out = (git_config_backend *)backend; @@ -656,10 +556,9 @@ static char *escape_value(const char *ptr) return git_buf_detach(&buf); } -static int parse_include(git_config_parser *reader, - diskfile_parse_state *parse_data, const char *file) +static int parse_include(config_file_parse_data *parse_data, const char *file) { - struct config_file *include; + config_file *include; git_buf path = GIT_BUF_INIT; char *dir; int result; @@ -667,7 +566,7 @@ static int parse_include(git_config_parser *reader, if (!file) return 0; - if ((result = git_path_dirname_r(&path, reader->file->path)) < 0) + if ((result = git_path_dirname_r(&path, parse_data->file->path)) < 0) return result; dir = git_buf_detach(&path); @@ -677,14 +576,14 @@ static int parse_include(git_config_parser *reader, if (result < 0) return result; - include = git_array_alloc(reader->file->includes); + include = git_array_alloc(parse_data->file->includes); GIT_ERROR_CHECK_ALLOC(include); memset(include, 0, sizeof(*include)); git_array_init(include->includes); include->path = git_buf_detach(&path); - result = config_read(parse_data->entries, parse_data->repo, - include, parse_data->level, parse_data->depth+1); + result = config_file_read(parse_data->entries, parse_data->repo, include, + parse_data->level, parse_data->depth+1); if (result == GIT_ENOTFOUND) { git_error_clear(); @@ -698,41 +597,41 @@ static int do_match_gitdir( int *matches, const git_repository *repo, const char *cfg_file, - const char *value, + const char *condition, bool case_insensitive) { - git_buf path = GIT_BUF_INIT; - int error, fnmatch_flags; + git_buf pattern = GIT_BUF_INIT, gitdir = GIT_BUF_INIT; + int error; - if (value[0] == '.' && git_path_is_dirsep(value[1])) { - git_path_dirname_r(&path, cfg_file); - git_buf_joinpath(&path, path.ptr, value + 2); - } else if (value[0] == '~' && git_path_is_dirsep(value[1])) - git_sysdir_expand_global_file(&path, value + 1); - else if (!git_path_is_absolute(value)) - git_buf_joinpath(&path, "**", value); + if (condition[0] == '.' && git_path_is_dirsep(condition[1])) { + git_path_dirname_r(&pattern, cfg_file); + git_buf_joinpath(&pattern, pattern.ptr, condition + 2); + } else if (condition[0] == '~' && git_path_is_dirsep(condition[1])) + git_sysdir_expand_global_file(&pattern, condition + 1); + else if (!git_path_is_absolute(condition)) + git_buf_joinpath(&pattern, "**", condition); else - git_buf_sets(&path, value); + git_buf_sets(&pattern, condition); - if (git_buf_oom(&path)) { + if (git_path_is_dirsep(condition[strlen(condition) - 1])) + git_buf_puts(&pattern, "**"); + + if (git_buf_oom(&pattern)) { error = -1; goto out; } - if (git_path_is_dirsep(value[strlen(value) - 1])) - git_buf_puts(&path, "**"); - - fnmatch_flags = FNM_PATHNAME|FNM_LEADING_DIR; - if (case_insensitive) - fnmatch_flags |= FNM_IGNORECASE; - - if ((error = p_fnmatch(path.ptr, git_repository_path(repo), fnmatch_flags)) < 0) + if ((error = git_repository_item_path(&gitdir, repo, GIT_REPOSITORY_ITEM_GITDIR)) < 0) goto out; - *matches = (error == 0); + if (git_path_is_dirsep(gitdir.ptr[gitdir.size - 1])) + git_buf_truncate(&gitdir, gitdir.size - 1); + *matches = wildmatch(pattern.ptr, gitdir.ptr, + WM_PATHNAME | (case_insensitive ? WM_CASEFOLD : 0)) == WM_MATCH; out: - git_buf_dispose(&path); + git_buf_dispose(&pattern); + git_buf_dispose(&gitdir); return error; } @@ -754,16 +653,67 @@ static int conditional_match_gitdir_i( return do_match_gitdir(matches, repo, cfg_file, value, true); } +static int conditional_match_onbranch( + int *matches, + const git_repository *repo, + const char *cfg_file, + const char *condition) +{ + git_buf reference = GIT_BUF_INIT, buf = GIT_BUF_INIT; + int error; + + GIT_UNUSED(cfg_file); + + /* + * NOTE: you cannot use `git_repository_head` here. Looking up the + * HEAD reference will create the ODB, which causes us to read the + * repo's config for keys like core.precomposeUnicode. As we're + * just parsing the config right now, though, this would result in + * an endless recursion. + */ + + if ((error = git_buf_joinpath(&buf, git_repository_path(repo), GIT_HEAD_FILE)) < 0 || + (error = git_futils_readbuffer(&reference, buf.ptr)) < 0) + goto out; + git_buf_rtrim(&reference); + + if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF))) + goto out; + git_buf_consume(&reference, reference.ptr + strlen(GIT_SYMREF)); + + if (git__strncmp(reference.ptr, GIT_REFS_HEADS_DIR, strlen(GIT_REFS_HEADS_DIR))) + goto out; + git_buf_consume(&reference, reference.ptr + strlen(GIT_REFS_HEADS_DIR)); + + /* + * If the condition ends with a '/', then we should treat it as if + * it had '**' appended. + */ + if ((error = git_buf_sets(&buf, condition)) < 0) + goto out; + if (git_path_is_dirsep(condition[strlen(condition) - 1]) && + (error = git_buf_puts(&buf, "**")) < 0) + goto out; + + *matches = wildmatch(buf.ptr, reference.ptr, WM_PATHNAME) == WM_MATCH; +out: + git_buf_dispose(&reference); + git_buf_dispose(&buf); + + return error; + +} + static const struct { const char *prefix; int (*matches)(int *matches, const git_repository *repo, const char *cfg, const char *value); } conditions[] = { { "gitdir:", conditional_match_gitdir }, - { "gitdir/i:", conditional_match_gitdir_i } + { "gitdir/i:", conditional_match_gitdir_i }, + { "onbranch:", conditional_match_onbranch } }; -static int parse_conditional_include(git_config_parser *reader, - diskfile_parse_state *parse_data, const char *section, const char *file) +static int parse_conditional_include(config_file_parse_data *parse_data, const char *section, const char *file) { char *condition; size_t i; @@ -781,12 +731,12 @@ static int parse_conditional_include(git_config_parser *reader, if ((error = conditions[i].matches(&matches, parse_data->repo, - parse_data->file_path, + parse_data->file->path, condition + strlen(conditions[i].prefix))) < 0) break; if (matches) - error = parse_include(reader, parse_data, file); + error = parse_include(parse_data, file); break; } @@ -804,12 +754,13 @@ static int read_on_variable( size_t line_len, void *data) { - diskfile_parse_state *parse_data = (diskfile_parse_state *)data; + config_file_parse_data *parse_data = (config_file_parse_data *)data; git_buf buf = GIT_BUF_INIT; git_config_entry *entry; const char *c; int result = 0; + GIT_UNUSED(reader); GIT_UNUSED(line); GIT_UNUSED(line_len); @@ -842,25 +793,25 @@ static int read_on_variable( /* Add or append the new config option */ if (!git__strcmp(entry->name, "include.path")) - result = parse_include(reader, parse_data, entry->value); + result = parse_include(parse_data, entry->value); else if (!git__prefixcmp(entry->name, "includeif.") && !git__suffixcmp(entry->name, ".path")) - result = parse_conditional_include(reader, parse_data, - entry->name, entry->value); + result = parse_conditional_include(parse_data, entry->name, entry->value); return result; } -static int config_read( +static int config_file_read_buffer( git_config_entries *entries, const git_repository *repo, - git_config_file *file, + config_file *file, git_config_level_t level, - int depth) + int depth, + const char *buf, + size_t buflen) { - diskfile_parse_state parse_data; + config_file_parse_data parse_data; git_config_parser reader; - git_buf contents = GIT_BUF_INIT; int error; if (depth >= MAX_INCLUDE_DEPTH) { @@ -868,30 +819,55 @@ static int config_read( return -1; } - if ((error = git_futils_readbuffer(&contents, file->path)) < 0) - goto out; - - git_parse_ctx_init(&reader.ctx, contents.ptr, contents.size); - - if ((error = git_hash_buf(&file->checksum, contents.ptr, contents.size)) < 0) - goto out; - /* Initialize the reading position */ - reader.file = file; - git_parse_ctx_init(&reader.ctx, contents.ptr, contents.size); + reader.path = file->path; + git_parse_ctx_init(&reader.ctx, buf, buflen); /* If the file is empty, there's nothing for us to do */ - if (!reader.ctx.content || *reader.ctx.content == '\0') + if (!reader.ctx.content || *reader.ctx.content == '\0') { + error = 0; goto out; + } parse_data.repo = repo; - parse_data.file_path = file->path; + parse_data.file = file; parse_data.entries = entries; parse_data.level = level; parse_data.depth = depth; error = git_config_parse(&reader, NULL, read_on_variable, NULL, NULL, &parse_data); +out: + return error; +} + +static int config_file_read( + git_config_entries *entries, + const git_repository *repo, + config_file *file, + git_config_level_t level, + int depth) +{ + git_buf contents = GIT_BUF_INIT; + struct stat st; + int error; + + if (p_stat(file->path, &st) < 0) { + error = git_path_set_error(errno, file->path, "stat"); + goto out; + } + + if ((error = git_futils_readbuffer(&contents, file->path)) < 0) + goto out; + + git_futils_filestamp_set_from_stat(&file->stamp, &st); + if ((error = git_hash_buf(&file->checksum, contents.ptr, contents.size)) < 0) + goto out; + + if ((error = config_file_read_buffer(entries, repo, file, level, depth, + contents.ptr, contents.size)) < 0) + goto out; + out: git_buf_dispose(&contents); return error; @@ -954,7 +930,7 @@ struct write_data { const char *section; const char *orig_name; const char *name; - const regex_t *preg; + const git_regexp *preg; const char *value; }; @@ -1059,7 +1035,7 @@ static int write_on_variable( /* If we have a regex to match the value, see if it matches */ if (has_matched && write_data->preg != NULL) - has_matched = (regexec(write_data->preg, var_value, 0, NULL, 0) == 0); + has_matched = (git_regexp_match(write_data->preg, var_value) == 0); /* If this isn't the name/value we're looking for, simply dump the * existing data back out and continue on. @@ -1120,39 +1096,33 @@ static int write_on_eof( /* * This is pretty much the parsing, except we write out anything we don't have */ -static int config_write(diskfile_backend *cfg, const char *orig_key, const char *key, const regex_t *preg, const char* value) -{ - int result; - char *orig_section, *section, *orig_name, *name, *ldot; - git_filebuf file = GIT_FILEBUF_INIT; - git_buf buf = GIT_BUF_INIT, contents = GIT_BUF_INIT; - git_config_parser reader; - struct write_data write_data; +static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char* value) - memset(&reader, 0, sizeof(reader)); - reader.file = &cfg->file; +{ + char *orig_section = NULL, *section = NULL, *orig_name, *name, *ldot; + git_buf buf = GIT_BUF_INIT, contents = GIT_BUF_INIT; + git_config_parser parser = GIT_CONFIG_PARSER_INIT; + git_filebuf file = GIT_FILEBUF_INIT; + struct write_data write_data; + int error; + + memset(&write_data, 0, sizeof(write_data)); if (cfg->locked) { - result = git_buf_puts(&contents, git_buf_cstr(&cfg->locked_content) == NULL ? "" : git_buf_cstr(&cfg->locked_content)); + error = git_buf_puts(&contents, git_buf_cstr(&cfg->locked_content) == NULL ? "" : git_buf_cstr(&cfg->locked_content)); } else { - /* Lock the file */ - if ((result = git_filebuf_open( - &file, cfg->file.path, GIT_FILEBUF_HASH_CONTENTS, GIT_CONFIG_FILE_MODE)) < 0) { - git_buf_dispose(&contents); - return result; - } + if ((error = git_filebuf_open(&file, cfg->file.path, GIT_FILEBUF_HASH_CONTENTS, + GIT_CONFIG_FILE_MODE)) < 0) + goto done; /* We need to read in our own config file */ - result = git_futils_readbuffer(&contents, cfg->file.path); + error = git_futils_readbuffer(&contents, cfg->file.path); } + if (error < 0 && error != GIT_ENOTFOUND) + goto done; - /* Initialise the reading position */ - if (result == 0 || result == GIT_ENOTFOUND) { - git_parse_ctx_init(&reader.ctx, contents.ptr, contents.size); - } else { - git_filebuf_cleanup(&file); - return -1; /* OS error when reading the file */ - } + if ((git_config_parser_init(&parser, cfg->file.path, contents.ptr, contents.size)) < 0) + goto done; ldot = strrchr(key, '.'); name = ldot + 1; @@ -1165,30 +1135,16 @@ static int config_write(diskfile_backend *cfg, const char *orig_key, const char GIT_ERROR_CHECK_ALLOC(orig_section); write_data.buf = &buf; - git_buf_init(&write_data.buffered_comment, 0); write_data.orig_section = orig_section; write_data.section = section; - write_data.in_section = 0; - write_data.preg_replaced = 0; write_data.orig_name = orig_name; write_data.name = name; write_data.preg = preg; write_data.value = value; - result = git_config_parse(&reader, - write_on_section, - write_on_variable, - write_on_comment, - write_on_eof, - &write_data); - git__free(section); - git__free(orig_section); - git_buf_dispose(&write_data.buffered_comment); - - if (result < 0) { - git_filebuf_cleanup(&file); + if ((error = git_config_parse(&parser, write_on_section, write_on_variable, + write_on_comment, write_on_eof, &write_data)) < 0) goto done; - } if (cfg->locked) { size_t len = buf.asize; @@ -1197,12 +1153,22 @@ static int config_write(diskfile_backend *cfg, const char *orig_key, const char git_buf_attach(&cfg->locked_content, git_buf_detach(&buf), len); } else { git_filebuf_write(&file, git_buf_cstr(&buf), git_buf_len(&buf)); - result = git_filebuf_commit(&file); + + if ((error = git_filebuf_commit(&file)) < 0) + goto done; + + if ((error = config_file_refresh_from_buffer(&cfg->parent, buf.ptr, buf.size)) < 0) + goto done; } done: + git__free(section); + git__free(orig_section); + git_buf_dispose(&write_data.buffered_comment); git_buf_dispose(&buf); git_buf_dispose(&contents); - git_parse_ctx_clear(&reader.ctx); - return result; + git_filebuf_cleanup(&file); + git_config_parser_dispose(&parser); + + return error; } diff --git a/src/config_mem.c b/src/config_mem.c index c2ecfda12..5b573a995 100644 --- a/src/config_mem.c +++ b/src/config_mem.c @@ -78,20 +78,24 @@ static int read_variable_cb( static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo) { config_memory_backend *memory_backend = (config_memory_backend *) backend; + git_config_parser parser = GIT_PARSE_CTX_INIT; config_memory_parse_data parse_data; - git_config_parser reader; + int error; GIT_UNUSED(repo); - if (memory_backend->cfg.size == 0) - return 0; - - git_parse_ctx_init(&reader.ctx, memory_backend->cfg.ptr, memory_backend->cfg.size); - reader.file = NULL; + if ((error = git_config_parser_init(&parser, "in-memory", memory_backend->cfg.ptr, + memory_backend->cfg.size)) < 0) + goto out; parse_data.entries = memory_backend->entries; parse_data.level = level; - return git_config_parse(&reader, NULL, read_variable_cb, NULL, NULL, &parse_data); + if ((error = git_config_parse(&parser, NULL, read_variable_cb, NULL, NULL, &parse_data)) < 0) + goto out; + +out: + git_config_parser_dispose(&parser); + return error; } static int config_memory_get(git_config_backend *backend, const char *key, git_config_entry **out) @@ -166,14 +170,6 @@ static int config_memory_unlock(git_config_backend *backend, int success) return config_error_readonly(); } -static int config_memory_snapshot(git_config_backend **out, git_config_backend *backend) -{ - GIT_UNUSED(out); - GIT_UNUSED(backend); - git_error_set(GIT_ERROR_CONFIG, "this backend does not support snapshots"); - return -1; -} - static void config_memory_free(git_config_backend *_backend) { config_memory_backend *backend = (config_memory_backend *)_backend; @@ -215,7 +211,7 @@ int git_config_backend_from_string(git_config_backend **out, const char *cfg, si backend->parent.iterator = config_memory_iterator; backend->parent.lock = config_memory_lock; backend->parent.unlock = config_memory_unlock; - backend->parent.snapshot = config_memory_snapshot; + backend->parent.snapshot = git_config_backend_snapshot; backend->parent.free = config_memory_free; *out = (git_config_backend *)backend; diff --git a/src/config_parse.c b/src/config_parse.c index ab2fb9867..48ad1164f 100644 --- a/src/config_parse.c +++ b/src/config_parse.c @@ -16,9 +16,14 @@ const char *git_config_escaped = "\n\t\b\"\\"; static void set_parse_error(git_config_parser *reader, int col, const char *error_str) { - const char *file = reader->file ? reader->file->path : "in-memory"; - git_error_set(GIT_ERROR_CONFIG, "failed to parse config file: %s (in %s:%"PRIuZ", column %d)", - error_str, file, reader->ctx.line_num, col); + if (col) + git_error_set(GIT_ERROR_CONFIG, + "failed to parse config file: %s (in %s:%"PRIuZ", column %d)", + error_str, reader->path, reader->ctx.line_num, col); + else + git_error_set(GIT_ERROR_CONFIG, + "failed to parse config file: %s (in %s:%"PRIuZ")", + error_str, reader->path, reader->ctx.line_num); } @@ -59,31 +64,35 @@ static int strip_comments(char *line, int in_quotes) } -static int parse_section_header_ext(git_config_parser *reader, const char *line, const char *base_name, char **section_name) +static int parse_subsection_header(git_config_parser *reader, const char *line, size_t pos, const char *base_name, char **section_name) { int c, rpos; - char *first_quote, *last_quote; + const char *first_quote, *last_quote; const char *line_start = line; git_buf buf = GIT_BUF_INIT; size_t quoted_len, alloc_len, base_name_len = strlen(base_name); - /* - * base_name is what came before the space. We should be at the - * first quotation mark, except for now, line isn't being kept in - * sync so we only really use it to calculate the length. - */ + /* Skip any additional whitespace before our section name */ + while (git__isspace(line[pos])) + pos++; - first_quote = strchr(line, '"'); - if (first_quote == NULL) { - set_parse_error(reader, 0, "Missing quotation marks in section header"); + /* We should be at the first quotation mark. */ + if (line[pos] != '"') { + set_parse_error(reader, 0, "missing quotation marks in section header"); goto end_error; } + first_quote = &line[pos]; last_quote = strrchr(line, '"'); quoted_len = last_quote - first_quote; + if ((last_quote - line) > INT_MAX) { + set_parse_error(reader, 0, "invalid section header, line too long"); + goto end_error; + } + if (quoted_len == 0) { - set_parse_error(reader, 0, "Missing closing quotation mark in section header"); + set_parse_error(reader, 0, "missing closing quotation mark in section header"); goto end_error; } @@ -107,7 +116,7 @@ static int parse_section_header_ext(git_config_parser *reader, const char *line, switch (c) { case 0: - set_parse_error(reader, 0, "Unexpected end-of-line in section header"); + set_parse_error(reader, 0, "unexpected end-of-line in section header"); goto end_error; case '"': @@ -117,7 +126,7 @@ static int parse_section_header_ext(git_config_parser *reader, const char *line, c = line[++rpos]; if (c == 0) { - set_parse_error(reader, rpos, "Unexpected end-of-line in section header"); + set_parse_error(reader, rpos, "unexpected end-of-line in section header"); goto end_error; } @@ -134,13 +143,13 @@ end_parse: goto end_error; if (line[rpos] != '"' || line[rpos + 1] != ']') { - set_parse_error(reader, rpos, "Unexpected text after closing quotes"); + set_parse_error(reader, rpos, "unexpected text after closing quotes"); git_buf_dispose(&buf); return -1; } *section_name = git_buf_detach(&buf); - return &line[rpos + 2] - line_start; /* rpos is at the closing quote */ + return (int)(&line[rpos + 2] - line_start); /* rpos is at the closing quote */ end_error: git_buf_dispose(&buf); @@ -165,7 +174,7 @@ static int parse_section_header(git_config_parser *reader, char **section_out) name_end = strrchr(line, ']'); if (name_end == NULL) { git__free(line); - set_parse_error(reader, 0, "Missing ']' in section header"); + set_parse_error(reader, 0, "missing ']' in section header"); return -1; } @@ -185,14 +194,14 @@ static int parse_section_header(git_config_parser *reader, char **section_out) do { if (git__isspace(c)){ name[name_length] = '\0'; - result = parse_section_header_ext(reader, line, name, section_out); + result = parse_subsection_header(reader, line, pos, name, section_out); git__free(line); git__free(name); return result; } if (!config_keychar(c) && c != '.') { - set_parse_error(reader, pos, "Unexpected character in header"); + set_parse_error(reader, pos, "unexpected character in header"); goto fail_parse; } @@ -201,7 +210,7 @@ static int parse_section_header(git_config_parser *reader, char **section_out) } while ((c = line[pos++]) != ']'); if (line[pos - 1] != ']') { - set_parse_error(reader, pos, "Unexpected end of file"); + set_parse_error(reader, pos, "unexpected end of file"); goto fail_parse; } @@ -386,7 +395,7 @@ static int parse_name( name_end++; if (line == name_end) { - set_parse_error(reader, 0, "Invalid configuration key"); + set_parse_error(reader, 0, "invalid configuration key"); return -1; } @@ -398,7 +407,7 @@ static int parse_name( if (*value_start == '=') { *value = value_start + 1; } else if (*value_start) { - set_parse_error(reader, 0, "Invalid configuration key"); + set_parse_error(reader, 0, "invalid configuration key"); return -1; } @@ -465,13 +474,24 @@ out: return error; } +int git_config_parser_init(git_config_parser *out, const char *path, const char *data, size_t datalen) +{ + out->path = path; + return git_parse_ctx_init(&out->ctx, data, datalen); +} + +void git_config_parser_dispose(git_config_parser *parser) +{ + git_parse_ctx_clear(&parser->ctx); +} + int git_config_parse( git_config_parser *parser, git_config_parser_section_cb on_section, git_config_parser_variable_cb on_variable, git_config_parser_comment_cb on_comment, git_config_parser_eof_cb on_eof, - void *data) + void *payload) { git_parse_ctx *ctx; char *current_section = NULL, *var_name = NULL, *var_value = NULL; @@ -511,7 +531,7 @@ int git_config_parse( git_parse_advance_chars(ctx, result); if (on_section) - result = on_section(parser, current_section, line_start, line_len, data); + result = on_section(parser, current_section, line_start, line_len, payload); /* * After we've parsed the section header we may not be * done with the line. If there's still data in there, @@ -531,13 +551,13 @@ int git_config_parse( case ';': case '#': if (on_comment) { - result = on_comment(parser, line_start, line_len, data); + result = on_comment(parser, line_start, line_len, payload); } break; default: /* assume variable declaration */ if ((result = parse_variable(parser, &var_name, &var_value)) == 0 && on_variable) { - result = on_variable(parser, current_section, var_name, var_value, line_start, line_len, data); + result = on_variable(parser, current_section, var_name, var_value, line_start, line_len, payload); git__free(var_name); git__free(var_value); } @@ -550,7 +570,7 @@ int git_config_parse( } if (on_eof) - result = on_eof(parser, current_section, data); + result = on_eof(parser, current_section, payload); out: git__free(current_section); diff --git a/src/config_parse.h b/src/config_parse.h index 81a13fceb..b791d3245 100644 --- a/src/config_parse.h +++ b/src/config_parse.h @@ -8,30 +8,28 @@ #define INCLUDE_config_parse_h__ #include "common.h" + #include "array.h" +#include "futils.h" #include "oid.h" #include "parse.h" extern const char *git_config_escapes; extern const char *git_config_escaped; -typedef struct config_file { - git_oid checksum; - char *path; - git_array_t(struct config_file) includes; -} git_config_file; - typedef struct { - struct config_file *file; + const char *path; git_parse_ctx ctx; } git_config_parser; +#define GIT_CONFIG_PARSER_INIT { NULL, GIT_PARSE_CTX_INIT } + typedef int (*git_config_parser_section_cb)( git_config_parser *parser, const char *current_section, const char *line, size_t line_len, - void *data); + void *payload); typedef int (*git_config_parser_variable_cb)( git_config_parser *parser, @@ -40,18 +38,21 @@ typedef int (*git_config_parser_variable_cb)( const char *var_value, const char *line, size_t line_len, - void *data); + void *payload); typedef int (*git_config_parser_comment_cb)( git_config_parser *parser, const char *line, size_t line_len, - void *data); + void *payload); typedef int (*git_config_parser_eof_cb)( git_config_parser *parser, const char *current_section, - void *data); + void *payload); + +int git_config_parser_init(git_config_parser *out, const char *path, const char *data, size_t datalen); +void git_config_parser_dispose(git_config_parser *parser); int git_config_parse( git_config_parser *parser, @@ -59,6 +60,6 @@ int git_config_parse( git_config_parser_variable_cb on_variable, git_config_parser_comment_cb on_comment, git_config_parser_eof_cb on_eof, - void *data); + void *payload); #endif diff --git a/src/config_snapshot.c b/src/config_snapshot.c new file mode 100644 index 000000000..62b9068fb --- /dev/null +++ b/src/config_snapshot.c @@ -0,0 +1,206 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "config.h" + +#include "config_entries.h" + +typedef struct { + git_config_backend parent; + git_mutex values_mutex; + git_config_entries *entries; + git_config_backend *source; +} config_snapshot_backend; + +static int config_error_readonly(void) +{ + git_error_set(GIT_ERROR_CONFIG, "this backend is read-only"); + return -1; +} + +static int config_snapshot_iterator( + git_config_iterator **iter, + struct git_config_backend *backend) +{ + config_snapshot_backend *b = GIT_CONTAINER_OF(backend, config_snapshot_backend, parent); + git_config_entries *entries = NULL; + int error; + + if ((error = git_config_entries_dup(&entries, b->entries)) < 0 || + (error = git_config_entries_iterator_new(iter, entries)) < 0) + goto out; + +out: + /* Let iterator delete duplicated entries when it's done */ + git_config_entries_free(entries); + return error; +} + +/* release the map containing the entry as an equivalent to freeing it */ +static void config_snapshot_entry_free(git_config_entry *entry) +{ + git_config_entries *entries = (git_config_entries *) entry->payload; + git_config_entries_free(entries); +} + +static int config_snapshot_get(git_config_backend *cfg, const char *key, git_config_entry **out) +{ + config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent); + git_config_entries *entries = NULL; + git_config_entry *entry; + int error = 0; + + if (git_mutex_lock(&b->values_mutex) < 0) { + git_error_set(GIT_ERROR_OS, "failed to lock config backend"); + return -1; + } + + entries = b->entries; + git_config_entries_incref(entries); + git_mutex_unlock(&b->values_mutex); + + if ((error = (git_config_entries_get(&entry, entries, key))) < 0) { + git_config_entries_free(entries); + return error; + } + + entry->free = config_snapshot_entry_free; + entry->payload = entries; + *out = entry; + + return 0; +} + +static int config_snapshot_set(git_config_backend *cfg, const char *name, const char *value) +{ + GIT_UNUSED(cfg); + GIT_UNUSED(name); + GIT_UNUSED(value); + + return config_error_readonly(); +} + +static int config_snapshot_set_multivar( + git_config_backend *cfg, const char *name, const char *regexp, const char *value) +{ + GIT_UNUSED(cfg); + GIT_UNUSED(name); + GIT_UNUSED(regexp); + GIT_UNUSED(value); + + return config_error_readonly(); +} + +static int config_snapshot_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) +{ + GIT_UNUSED(cfg); + GIT_UNUSED(name); + GIT_UNUSED(regexp); + + return config_error_readonly(); +} + +static int config_snapshot_delete(git_config_backend *cfg, const char *name) +{ + GIT_UNUSED(cfg); + GIT_UNUSED(name); + + return config_error_readonly(); +} + +static int config_snapshot_lock(git_config_backend *_cfg) +{ + GIT_UNUSED(_cfg); + + return config_error_readonly(); +} + +static int config_snapshot_unlock(git_config_backend *_cfg, int success) +{ + GIT_UNUSED(_cfg); + GIT_UNUSED(success); + + return config_error_readonly(); +} + +static void config_snapshot_free(git_config_backend *_backend) +{ + config_snapshot_backend *backend = GIT_CONTAINER_OF(_backend, config_snapshot_backend, parent); + + if (backend == NULL) + return; + + git_config_entries_free(backend->entries); + git_mutex_free(&backend->values_mutex); + git__free(backend); +} + +static int config_snapshot_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo) +{ + config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent); + git_config_entries *entries = NULL; + git_config_iterator *it = NULL; + git_config_entry *entry; + int error; + + /* We're just copying data, don't care about the level or repo*/ + GIT_UNUSED(level); + GIT_UNUSED(repo); + + if ((error = git_config_entries_new(&entries)) < 0 || + (error = b->source->iterator(&it, b->source)) < 0) + goto out; + + while ((error = git_config_next(&entry, it)) == 0) + if ((error = git_config_entries_dup_entry(entries, entry)) < 0) + goto out; + + if (error < 0) { + if (error != GIT_ITEROVER) + goto out; + error = 0; + } + + b->entries = entries; + +out: + git_config_iterator_free(it); + if (error) + git_config_entries_free(entries); + return error; +} + +int git_config_backend_snapshot(git_config_backend **out, git_config_backend *source) +{ + config_snapshot_backend *backend; + + backend = git__calloc(1, sizeof(config_snapshot_backend)); + GIT_ERROR_CHECK_ALLOC(backend); + + backend->parent.version = GIT_CONFIG_BACKEND_VERSION; + git_mutex_init(&backend->values_mutex); + + backend->source = source; + + backend->parent.readonly = 1; + backend->parent.version = GIT_CONFIG_BACKEND_VERSION; + backend->parent.open = config_snapshot_open; + backend->parent.get = config_snapshot_get; + backend->parent.set = config_snapshot_set; + backend->parent.set_multivar = config_snapshot_set_multivar; + backend->parent.snapshot = git_config_backend_snapshot; + backend->parent.del = config_snapshot_delete; + backend->parent.del_multivar = config_snapshot_delete_multivar; + backend->parent.iterator = config_snapshot_iterator; + backend->parent.lock = config_snapshot_lock; + backend->parent.unlock = config_snapshot_unlock; + backend->parent.free = config_snapshot_free; + + *out = &backend->parent; + + return 0; +} diff --git a/src/crlf.c b/src/crlf.c index 80441e7e3..81b5216bc 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -12,7 +12,7 @@ #include "git2/index.h" #include "git2/sys/filter.h" -#include "fileops.h" +#include "futils.h" #include "hash.h" #include "filter.h" #include "buf_text.h" @@ -44,11 +44,11 @@ struct crlf_filter { static git_crlf_t check_crlf(const char *value) { - if (GIT_ATTR_TRUE(value)) + if (GIT_ATTR_IS_TRUE(value)) return GIT_CRLF_TEXT; - else if (GIT_ATTR_FALSE(value)) + else if (GIT_ATTR_IS_FALSE(value)) return GIT_CRLF_BINARY; - else if (GIT_ATTR_UNSPECIFIED(value)) + else if (GIT_ATTR_IS_UNSPECIFIED(value)) ; else if (strcmp(value, "input") == 0) return GIT_CRLF_TEXT_INPUT; @@ -58,9 +58,9 @@ static git_crlf_t check_crlf(const char *value) return GIT_CRLF_UNDEFINED; } -static git_cvar_value check_eol(const char *value) +static git_configmap_value check_eol(const char *value) { - if (GIT_ATTR_UNSPECIFIED(value)) + if (GIT_ATTR_IS_UNSPECIFIED(value)) ; else if (strcmp(value, "lf") == 0) return GIT_EOL_LF; @@ -78,7 +78,7 @@ static int has_cr_in_index(const git_filter_source *src) const git_index_entry *entry; git_blob *blob; const void *blobcontent; - git_off_t blobsize; + git_object_size_t blobsize; bool found_cr; if (!path) @@ -127,7 +127,7 @@ static int text_eol_is_crlf(struct crlf_attrs *ca) return 0; } -static git_cvar_value output_eol(struct crlf_attrs *ca) +static git_configmap_value output_eol(struct crlf_attrs *ca) { switch (ca->crlf_action) { case GIT_CRLF_BINARY: @@ -293,12 +293,12 @@ static int convert_attrs( memset(ca, 0, sizeof(struct crlf_attrs)); - if ((error = git_repository__cvar(&ca->auto_crlf, - git_filter_source_repo(src), GIT_CVAR_AUTO_CRLF)) < 0 || - (error = git_repository__cvar(&ca->safe_crlf, - git_filter_source_repo(src), GIT_CVAR_SAFE_CRLF)) < 0 || - (error = git_repository__cvar(&ca->core_eol, - git_filter_source_repo(src), GIT_CVAR_EOL)) < 0) + if ((error = git_repository__configmap_lookup(&ca->auto_crlf, + git_filter_source_repo(src), GIT_CONFIGMAP_AUTO_CRLF)) < 0 || + (error = git_repository__configmap_lookup(&ca->safe_crlf, + git_filter_source_repo(src), GIT_CONFIGMAP_SAFE_CRLF)) < 0 || + (error = git_repository__configmap_lookup(&ca->core_eol, + git_filter_source_repo(src), GIT_CONFIGMAP_EOL)) < 0) return error; /* downgrade FAIL to WARN if ALLOW_UNSAFE option is used */ diff --git a/src/describe.c b/src/describe.c index 893ca646e..42e5848c2 100644 --- a/src/describe.c +++ b/src/describe.c @@ -16,10 +16,11 @@ #include "commit_list.h" #include "oidmap.h" #include "refs.h" +#include "repository.h" #include "revwalk.h" #include "tag.h" #include "vector.h" -#include "repository.h" +#include "wildmatch.h" /* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */ @@ -36,12 +37,7 @@ struct commit_name { static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key) { - size_t pos = git_oidmap_lookup_index(map, key); - - if (!git_oidmap_valid_index(map, pos)) - return NULL; - - return git_oidmap_value_at(map, pos); + return git_oidmap_get(map, key); } static struct commit_name *find_commit_name( @@ -124,13 +120,8 @@ static int add_to_known_names( e->path = git__strdup(path); git_oid_cpy(&e->peeled, peeled); - if (!found) { - int ret; - - git_oidmap_insert(names, &e->peeled, e, &ret); - if (ret < 0) - return -1; - } + if (!found && git_oidmap_set(names, &e->peeled, e) < 0) + return -1; } else git_tag_free(tag); @@ -224,7 +215,7 @@ static int get_name(const char *refname, void *payload) return 0; /* Accept only tags that match the pattern, if given */ - if (data->opts->pattern && (!is_tag || p_fnmatch(data->opts->pattern, + if (data->opts->pattern && (!is_tag || wildmatch(data->opts->pattern, refname + strlen(GIT_REFS_TAGS_DIR), 0))) return 0; @@ -681,8 +672,8 @@ int git_describe_commit( "git_describe_options"); data.opts = &normalized; - data.names = git_oidmap_alloc(); - GIT_ERROR_CHECK_ALLOC(data.names); + if ((error = git_oidmap_new(&data.names)) < 0) + return error; /** TODO: contains to be implemented */ @@ -769,7 +760,7 @@ static int normalize_format_options( const git_describe_format_options *src) { if (!src) { - git_describe_init_format_options(dst, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION); + git_describe_format_options_init(dst, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION); return 0; } @@ -878,16 +869,26 @@ void git_describe_result_free(git_describe_result *result) git__free(result); } -int git_describe_init_options(git_describe_options *opts, unsigned int version) +int git_describe_options_init(git_describe_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_describe_options, GIT_DESCRIBE_OPTIONS_INIT); return 0; } -int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version) +int git_describe_init_options(git_describe_options *opts, unsigned int version) +{ + return git_describe_options_init(opts, version); +} + +int git_describe_format_options_init(git_describe_format_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_describe_format_options, GIT_DESCRIBE_FORMAT_OPTIONS_INIT); return 0; } + +int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version) +{ + return git_describe_format_options_init(opts, version); +} diff --git a/src/diff.c b/src/diff.c index d2e129d1b..47f49d949 100644 --- a/src/diff.c +++ b/src/diff.c @@ -323,7 +323,7 @@ int git_diff_commit_as_email( git_commit *commit, size_t patch_no, size_t total_patches, - git_diff_format_email_flags_t flags, + uint32_t flags, const git_diff_options *diff_opts) { git_diff *diff = NULL; @@ -350,14 +350,19 @@ int git_diff_commit_as_email( return error; } -int git_diff_init_options(git_diff_options *opts, unsigned int version) +int git_diff_options_init(git_diff_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT); return 0; } -int git_diff_find_init_options( +int git_diff_init_options(git_diff_options *opts, unsigned int version) +{ + return git_diff_options_init(opts, version); +} + +int git_diff_find_options_init( git_diff_find_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( @@ -365,7 +370,13 @@ int git_diff_find_init_options( return 0; } -int git_diff_format_email_init_options( +int git_diff_find_init_options( + git_diff_find_options *opts, unsigned int version) +{ + return git_diff_find_options_init(opts, version); +} + +int git_diff_format_email_options_init( git_diff_format_email_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( @@ -374,6 +385,12 @@ int git_diff_format_email_init_options( return 0; } +int git_diff_format_email_init_options( + git_diff_format_email_options *opts, unsigned int version) +{ + return git_diff_format_email_options_init(opts, version); +} + static int flush_hunk(git_oid *result, git_hash_ctx *ctx) { git_oid hash; @@ -409,41 +426,7 @@ static void strip_spaces(git_buf *buf) git_buf_truncate(buf, len); } -static int file_cb( - const git_diff_delta *delta, - float progress, - void *payload) -{ - struct patch_id_args *args = (struct patch_id_args *) payload; - git_buf buf = GIT_BUF_INIT; - int error; - - GIT_UNUSED(progress); - - if (!args->first_file && - (error = flush_hunk(&args->result, &args->ctx)) < 0) - goto out; - args->first_file = 0; - - if ((error = git_buf_printf(&buf, - "diff--gita/%sb/%s---a/%s+++b/%s", - delta->old_file.path, - delta->new_file.path, - delta->old_file.path, - delta->new_file.path)) < 0) - goto out; - - strip_spaces(&buf); - - if ((error = git_hash_update(&args->ctx, buf.ptr, buf.size)) < 0) - goto out; - -out: - git_buf_dispose(&buf); - return error; -} - -static int line_cb( +int git_diff_patchid_print_callback__to_buf( const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, @@ -451,37 +434,36 @@ static int line_cb( { struct patch_id_args *args = (struct patch_id_args *) payload; git_buf buf = GIT_BUF_INIT; - int error; + int error = 0; - GIT_UNUSED(delta); - GIT_UNUSED(hunk); + if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL || + line->origin == GIT_DIFF_LINE_ADD_EOFNL || + line->origin == GIT_DIFF_LINE_DEL_EOFNL) + goto out; - switch (line->origin) { - case GIT_DIFF_LINE_ADDITION: - git_buf_putc(&buf, '+'); - break; - case GIT_DIFF_LINE_DELETION: - git_buf_putc(&buf, '-'); - break; - case GIT_DIFF_LINE_CONTEXT: - break; - default: - git_error_set(GIT_ERROR_PATCH, "invalid line origin for patch"); - return -1; - } + if ((error = git_diff_print_callback__to_buf(delta, hunk, + line, &buf)) < 0) + goto out; - git_buf_put(&buf, line->content, line->content_len); strip_spaces(&buf); + if (line->origin == GIT_DIFF_LINE_FILE_HDR && + !args->first_file && + (error = flush_hunk(&args->result, &args->ctx) < 0)) + goto out; + if ((error = git_hash_update(&args->ctx, buf.ptr, buf.size)) < 0) goto out; + if (line->origin == GIT_DIFF_LINE_FILE_HDR && args->first_file) + args->first_file = 0; + out: git_buf_dispose(&buf); return error; } -int git_diff_patchid_init_options(git_diff_patchid_options *opts, unsigned int version) +int git_diff_patchid_options_init(git_diff_patchid_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_INIT); @@ -501,7 +483,10 @@ int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opt if ((error = git_hash_ctx_init(&args.ctx)) < 0) goto out; - if ((error = git_diff_foreach(diff, file_cb, NULL, NULL, line_cb, &args)) < 0) + if ((error = git_diff_print(diff, + GIT_DIFF_FORMAT_PATCH_ID, + git_diff_patchid_print_callback__to_buf, + &args)) < 0) goto out; if ((error = (flush_hunk(&args.result, &args.ctx))) < 0) diff --git a/src/diff.h b/src/diff.h index 93374b96e..69233b39f 100644 --- a/src/diff.h +++ b/src/diff.h @@ -39,8 +39,8 @@ struct git_diff { git_diff_options opts; git_vector deltas; /* vector of git_diff_delta */ git_pool pool; - git_iterator_type_t old_src; - git_iterator_type_t new_src; + git_iterator_t old_src; + git_iterator_t new_src; git_diff_perfdata perf; int (*strcomp)(const char *, const char *); @@ -57,7 +57,8 @@ extern int git_diff_delta__format_file_header( const git_diff_delta *delta, const char *oldpfx, const char *newpfx, - int oid_strlen); + int oid_strlen, + bool print_index); extern int git_diff_delta__cmp(const void *a, const void *b); extern int git_diff_delta__casecmp(const void *a, const void *b); diff --git a/src/diff_driver.c b/src/diff_driver.c index 05246dfa3..831d3262d 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -9,11 +9,13 @@ #include "git2/attr.h" +#include "common.h" #include "diff.h" #include "strmap.h" #include "map.h" #include "buf_text.h" #include "config.h" +#include "regexp.h" #include "repository.h" typedef enum { @@ -24,7 +26,7 @@ typedef enum { } git_diff_driver_t; typedef struct { - regex_t re; + git_regexp re; int flags; } git_diff_driver_pattern; @@ -38,7 +40,7 @@ struct git_diff_driver { uint32_t binary_flags; uint32_t other_flags; git_array_t(git_diff_driver_pattern) fn_patterns; - regex_t word_pattern; + git_regexp word_pattern; char name[GIT_FLEX_ARRAY]; }; @@ -63,7 +65,7 @@ git_diff_driver_registry *git_diff_driver_registry_new(void) if (!reg) return NULL; - if (git_strmap_alloc(®->drivers) < 0) { + if (git_strmap_new(®->drivers) < 0) { git_diff_driver_registry_free(reg); return NULL; } @@ -112,7 +114,7 @@ static int diff_driver_add_patterns( if (error < 0) break; - if ((error = p_regcomp(&pat->re, buf.ptr, regex_flags)) != 0) { + if ((error = git_regexp_compile(&pat->re, buf.ptr, regex_flags)) != 0) { /* * TODO: issue a warning */ @@ -129,7 +131,7 @@ static int diff_driver_add_patterns( static int diff_driver_xfuncname(const git_config_entry *entry, void *payload) { - return diff_driver_add_patterns(payload, entry->value, REG_EXTENDED); + return diff_driver_add_patterns(payload, entry->value, 0); } static int diff_driver_funcname(const git_config_entry *entry, void *payload) @@ -183,9 +185,9 @@ static int git_diff_driver_builtin( git_diff_driver_registry *reg, const char *driver_name) { - int error = 0; git_diff_driver_definition *ddef = NULL; git_diff_driver *drv = NULL; + int error = 0; size_t idx; for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) { @@ -204,20 +206,15 @@ static int git_diff_driver_builtin( if (ddef->fns && (error = diff_driver_add_patterns( - drv, ddef->fns, ddef->flags | REG_EXTENDED)) < 0) + drv, ddef->fns, ddef->flags)) < 0) goto done; if (ddef->words && - (error = p_regcomp( - &drv->word_pattern, ddef->words, ddef->flags | REG_EXTENDED))) - { - error = git_error_set_regex(&drv->word_pattern, error); + (error = git_regexp_compile(&drv->word_pattern, ddef->words, ddef->flags)) < 0) goto done; - } - git_strmap_insert(reg->drivers, drv->name, drv, &error); - if (error > 0) - error = 0; + if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) + goto done; done: if (error && drv) @@ -233,8 +230,8 @@ static int git_diff_driver_load( { int error = 0; git_diff_driver_registry *reg; - git_diff_driver *drv = NULL; - size_t namelen, pos; + git_diff_driver *drv; + size_t namelen; git_config *cfg = NULL; git_buf name = GIT_BUF_INIT; git_config_entry *ce = NULL; @@ -243,9 +240,8 @@ static int git_diff_driver_load( if ((reg = git_repository_driver_registry(repo)) == NULL) return -1; - pos = git_strmap_lookup_index(reg->drivers, driver_name); - if (git_strmap_valid_index(reg->drivers, pos)) { - *out = git_strmap_value_at(reg->drivers, pos); + if ((drv = git_strmap_get(reg->drivers, driver_name)) != NULL) { + *out = drv; return 0; } @@ -282,7 +278,9 @@ static int git_diff_driver_load( /* TODO: warn if diff..command or diff..textconv are set */ git_buf_truncate(&name, namelen + strlen("diff..")); - git_buf_put(&name, "xfuncname", strlen("xfuncname")); + if ((error = git_buf_PUTS(&name, "xfuncname")) < 0) + goto done; + if ((error = git_config_get_multivar_foreach( cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) { if (error != GIT_ENOTFOUND) @@ -291,7 +289,9 @@ static int git_diff_driver_load( } git_buf_truncate(&name, namelen + strlen("diff..")); - git_buf_put(&name, "funcname", strlen("funcname")); + if ((error = git_buf_PUTS(&name, "funcname")) < 0) + goto done; + if ((error = git_config_get_multivar_foreach( cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) { if (error != GIT_ENOTFOUND) @@ -306,16 +306,17 @@ static int git_diff_driver_load( } git_buf_truncate(&name, namelen + strlen("diff..")); - git_buf_put(&name, "wordregex", strlen("wordregex")); + if ((error = git_buf_PUTS(&name, "wordregex")) < 0) + goto done; + if ((error = git_config__lookup_entry(&ce, cfg, name.ptr, false)) < 0) goto done; if (!ce || !ce->value) /* no diff..wordregex, so just continue */; - else if (!(error = p_regcomp(&drv->word_pattern, ce->value, REG_EXTENDED))) + else if (!(error = git_regexp_compile(&drv->word_pattern, ce->value, 0))) found_driver = true; else { /* TODO: warn about bad regex instead of failure */ - error = git_error_set_regex(&drv->word_pattern, error); goto done; } @@ -328,10 +329,8 @@ static int git_diff_driver_load( goto done; /* store driver in registry */ - git_strmap_insert(reg->drivers, drv->name, drv, &error); - if (error < 0) + if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0) goto done; - error = 0; *out = drv; @@ -368,11 +367,11 @@ int git_diff_driver_lookup( attrsession, 0, path, 1, attrs)) < 0) /* return error below */; - else if (GIT_ATTR_UNSPECIFIED(values[0])) + else if (GIT_ATTR_IS_UNSPECIFIED(values[0])) /* just use the auto value */; - else if (GIT_ATTR_FALSE(values[0])) + else if (GIT_ATTR_IS_FALSE(values[0])) *out = &global_drivers[DIFF_DRIVER_BINARY]; - else if (GIT_ATTR_TRUE(values[0])) + else if (GIT_ATTR_IS_TRUE(values[0])) *out = &global_drivers[DIFF_DRIVER_TEXT]; /* otherwise look for driver information in config and build driver */ @@ -397,10 +396,10 @@ void git_diff_driver_free(git_diff_driver *driver) return; for (i = 0; i < git_array_size(driver->fn_patterns); ++i) - regfree(& git_array_get(driver->fn_patterns, i)->re); + git_regexp_dispose(& git_array_get(driver->fn_patterns, i)->re); git_array_clear(driver->fn_patterns); - regfree(&driver->word_pattern); + git_regexp_dispose(&driver->word_pattern); git__free(driver); } @@ -448,19 +447,19 @@ static int diff_context_line__pattern_match( git_diff_driver *driver, git_buf *line) { size_t i, maxi = git_array_size(driver->fn_patterns); - regmatch_t pmatch[2]; + git_regmatch pmatch[2]; for (i = 0; i < maxi; ++i) { git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i); - if (!regexec(&pat->re, line->ptr, 2, pmatch, 0)) { + if (!git_regexp_search(&pat->re, line->ptr, 2, pmatch)) { if (pat->flags & REG_NEGATE) return false; /* use pmatch data to trim line data */ - i = (pmatch[1].rm_so >= 0) ? 1 : 0; - git_buf_consume(line, git_buf_cstr(line) + pmatch[i].rm_so); - git_buf_truncate(line, pmatch[i].rm_eo - pmatch[i].rm_so); + i = (pmatch[1].start >= 0) ? 1 : 0; + git_buf_consume(line, git_buf_cstr(line) + pmatch[i].start); + git_buf_truncate(line, pmatch[i].end - pmatch[i].start); git_buf_rtrim(line); return true; diff --git a/src/diff_file.c b/src/diff_file.c index ae1016d9a..621bff556 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -12,7 +12,7 @@ #include "diff.h" #include "diff_generate.h" #include "odb.h" -#include "fileops.h" +#include "futils.h" #include "filter.h" #define DIFF_MAX_FILESIZE 0x20000000 @@ -50,8 +50,8 @@ static int diff_file_content_init_common( fc->opts_max_size = opts->max_size ? opts->max_size : DIFF_MAX_FILESIZE; - if (fc->src == GIT_ITERATOR_TYPE_EMPTY) - fc->src = GIT_ITERATOR_TYPE_TREE; + if (fc->src == GIT_ITERATOR_EMPTY) + fc->src = GIT_ITERATOR_TREE; if (!fc->driver && git_diff_driver_lookup(&fc->driver, fc->repo, @@ -62,7 +62,7 @@ static int diff_file_content_init_common( git_diff_driver_update_options(&fc->opts_flags, fc->driver); /* make sure file is conceivable mmap-able */ - if ((git_off_t)((size_t)fc->file->size) != fc->file->size) + if ((size_t)fc->file->size != fc->file->size) fc->file->flags |= GIT_DIFF_FLAG_BINARY; /* check if user is forcing text diff the file */ else if (fc->opts_flags & GIT_DIFF_FORCE_TEXT) { @@ -232,7 +232,7 @@ static int diff_file_content_load_blob( int error = 0; git_odb_object *odb_obj = NULL; - if (git_oid_iszero(&fc->file->id)) + if (git_oid_is_zero(&fc->file->id)) return 0; if (fc->file->mode == GIT_FILEMODE_COMMIT) @@ -290,8 +290,8 @@ static int diff_file_content_load_workdir_symlink( ssize_t alloc_len, read_len; int symlink_supported, error; - if ((error = git_repository__cvar( - &symlink_supported, fc->repo, GIT_CVAR_SYMLINKS)) < 0) + if ((error = git_repository__configmap_lookup( + &symlink_supported, fc->repo, GIT_CONFIGMAP_SYMLINKS)) < 0) return -1; if (!symlink_supported) @@ -330,8 +330,10 @@ static int diff_file_content_load_workdir_file( if (fd < 0) return fd; - if (!fc->file->size && - !(fc->file->size = git_futils_filesize(fd))) + if (!fc->file->size) + error = git_futils_filesize(&fc->file->size, fd); + + if (error < 0 || !fc->file->size) goto cleanup; if ((diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0 && @@ -423,7 +425,7 @@ int git_diff_file_content__load( (diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0) return 0; - if (fc->src == GIT_ITERATOR_TYPE_WORKDIR) + if (fc->src == GIT_ITERATOR_WORKDIR) error = diff_file_content_load_workdir(fc, diff_opts); else error = diff_file_content_load_blob(fc, diff_opts); diff --git a/src/diff_file.h b/src/diff_file.h index 5da7a7bb8..8d743e821 100644 --- a/src/diff_file.h +++ b/src/diff_file.h @@ -20,8 +20,8 @@ typedef struct { git_diff_driver *driver; uint32_t flags; uint32_t opts_flags; - git_off_t opts_max_size; - git_iterator_type_t src; + git_object_size_t opts_max_size; + git_iterator_t src; const git_blob *blob; git_map map; } git_diff_file_content; diff --git a/src/diff_generate.c b/src/diff_generate.c index 5579dc214..b69ef3032 100644 --- a/src/diff_generate.c +++ b/src/diff_generate.c @@ -9,7 +9,7 @@ #include "diff.h" #include "patch_generate.h" -#include "fileops.h" +#include "futils.h" #include "config.h" #include "attr_file.h" #include "filter.h" @@ -179,7 +179,7 @@ static int diff_delta__from_one( delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - if (has_old || !git_oid_iszero(&delta->new_file.id)) + if (has_old || !git_oid_is_zero(&delta->new_file.id)) delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; return diff_insert_delta(diff, delta, matched_pathspec); @@ -240,7 +240,7 @@ static int diff_delta__from_two( delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; - if (!git_oid_iszero(&new_entry->id)) + if (!git_oid_is_zero(&new_entry->id)) delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; } @@ -341,16 +341,16 @@ bool git_diff_delta__should_skip( static const char *diff_mnemonic_prefix( - git_iterator_type_t type, bool left_side) + git_iterator_t type, bool left_side) { const char *pfx = ""; switch (type) { - case GIT_ITERATOR_TYPE_EMPTY: pfx = "c"; break; - case GIT_ITERATOR_TYPE_TREE: pfx = "c"; break; - case GIT_ITERATOR_TYPE_INDEX: pfx = "i"; break; - case GIT_ITERATOR_TYPE_WORKDIR: pfx = "w"; break; - case GIT_ITERATOR_TYPE_FS: pfx = left_side ? "1" : "2"; break; + case GIT_ITERATOR_EMPTY: pfx = "c"; break; + case GIT_ITERATOR_TREE: pfx = "c"; break; + case GIT_ITERATOR_INDEX: pfx = "i"; break; + case GIT_ITERATOR_WORKDIR: pfx = "w"; break; + case GIT_ITERATOR_FS: pfx = left_side ? "1" : "2"; break; default: break; } @@ -472,17 +472,17 @@ static int diff_generated_apply_options( if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) return val; - if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val) + if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_SYMLINKS) && val) diff->diffcaps |= GIT_DIFFCAPS_HAS_SYMLINKS; - if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val) + if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_IGNORESTAT) && val) diff->diffcaps |= GIT_DIFFCAPS_IGNORE_STAT; if ((diff->base.opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && - !git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val) + !git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_FILEMODE) && val) diff->diffcaps |= GIT_DIFFCAPS_TRUST_MODE_BITS; - if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val) + if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_TRUSTCTIME) && val) diff->diffcaps |= GIT_DIFFCAPS_TRUST_CTIME; /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ @@ -497,17 +497,17 @@ static int diff_generated_apply_options( /* Reverse src info if diff is reversed */ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - git_iterator_type_t tmp_src = diff->base.old_src; + git_iterator_t tmp_src = diff->base.old_src; diff->base.old_src = diff->base.new_src; diff->base.new_src = tmp_src; } /* Unset UPDATE_INDEX unless diffing workdir and index */ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && - (!(diff->base.old_src == GIT_ITERATOR_TYPE_WORKDIR || - diff->base.new_src == GIT_ITERATOR_TYPE_WORKDIR) || - !(diff->base.old_src == GIT_ITERATOR_TYPE_INDEX || - diff->base.new_src == GIT_ITERATOR_TYPE_INDEX))) + (!(diff->base.old_src == GIT_ITERATOR_WORKDIR || + diff->base.new_src == GIT_ITERATOR_WORKDIR) || + !(diff->base.old_src == GIT_ITERATOR_INDEX || + diff->base.new_src == GIT_ITERATOR_INDEX))) diff->base.opts.flags &= ~GIT_DIFF_UPDATE_INDEX; /* if ignore_submodules not explicitly set, check diff config */ @@ -560,11 +560,11 @@ int git_diff__oid_for_file( git_diff *diff, const char *path, uint16_t mode, - git_off_t size) + git_object_size_t size) { git_index_entry entry; - if (size < 0 || size > UINT32_MAX) { + if (size > UINT32_MAX) { git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", path); return -1; } @@ -742,7 +742,7 @@ static int maybe_modified( const git_index_entry *nitem = info->nitem; unsigned int omode = oitem->mode; unsigned int nmode = nitem->mode; - bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR); + bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_WORKDIR); bool modified_uncertain = false; const char *matched_pathspec; int error = 0; @@ -797,13 +797,13 @@ static int maybe_modified( /* if oids and modes match (and are valid), then file is unmodified */ } else if (git_oid_equal(&oitem->id, &nitem->id) && omode == nmode && - !git_oid_iszero(&oitem->id)) { + !git_oid_is_zero(&oitem->id)) { status = GIT_DELTA_UNMODIFIED; /* if we have an unknown OID and a workdir iterator, then check some * circumstances that can accelerate things or need special handling */ - } else if (git_oid_iszero(&nitem->id) && new_is_workdir) { + } else if (git_oid_is_zero(&nitem->id) && new_is_workdir) { bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0); git_index *index = git_iterator_index(info->new_iter); @@ -843,7 +843,7 @@ static int maybe_modified( /* if we got here and decided that the files are modified, but we * haven't calculated the OID of the new item, then calculate it now */ - if (modified_uncertain && git_oid_iszero(&nitem->id)) { + if (modified_uncertain && git_oid_is_zero(&nitem->id)) { const git_oid *update_check = DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ? &oitem->id : NULL; @@ -877,7 +877,7 @@ static int maybe_modified( return diff_delta__from_two( diff, status, oitem, omode, nitem, nmode, - git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec); + git_oid_is_zero(&noid) ? NULL : &noid, matched_pathspec); } static bool entry_is_prefixed( @@ -1079,7 +1079,7 @@ static int handle_unmatched_new_item( /* item contained in ignored directory, so skip over it */ return iterator_advance(&info->nitem, info->new_iter); - else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) { + else if (info->new_iter->type != GIT_ITERATOR_WORKDIR) { if (delta_type != GIT_DELTA_CONFLICTED) delta_type = GIT_DELTA_ADDED; } @@ -1262,29 +1262,31 @@ cleanup: return error; } -#define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \ - git_iterator *a = NULL, *b = NULL; \ - char *pfx = (opts && !(opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) ? \ - git_pathspec_prefix(&opts->pathspec) : NULL; \ - git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, \ - b_opts = GIT_ITERATOR_OPTIONS_INIT; \ - a_opts.flags = FLAGS_FIRST; \ - a_opts.start = pfx; \ - a_opts.end = pfx; \ - b_opts.flags = FLAGS_SECOND; \ - b_opts.start = pfx; \ - b_opts.end = pfx; \ - GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \ - if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { \ - a_opts.pathlist.strings = opts->pathspec.strings; \ - a_opts.pathlist.count = opts->pathspec.count; \ - b_opts.pathlist.strings = opts->pathspec.strings; \ - b_opts.pathlist.count = opts->pathspec.count; \ - } \ - if (!error && !(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ - error = git_diff__from_iterators(&diff, repo, a, b, opts); \ - git__free(pfx); git_iterator_free(a); git_iterator_free(b); \ -} while (0) +static int diff_prepare_iterator_opts(char **prefix, git_iterator_options *a, int aflags, + git_iterator_options *b, int bflags, + const git_diff_options *opts) +{ + GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); + + *prefix = NULL; + + if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { + a->pathlist.strings = opts->pathspec.strings; + a->pathlist.count = opts->pathspec.count; + b->pathlist.strings = opts->pathspec.strings; + b->pathlist.count = opts->pathspec.count; + } else if (opts) { + *prefix = git_pathspec_prefix(&opts->pathspec); + GIT_ERROR_CHECK_ALLOC(prefix); + } + + a->flags = aflags; + b->flags = bflags; + a->start = b->start = *prefix; + a->end = b->end = *prefix; + + return 0; +} int git_diff_tree_to_tree( git_diff **out, @@ -1293,8 +1295,12 @@ int git_diff_tree_to_tree( git_tree *new_tree, const git_diff_options *opts) { - git_diff *diff = NULL; git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE; + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, + b_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator *a = NULL, *b = NULL; + git_diff *diff = NULL; + char *prefix = NULL; int error = 0; assert(out && repo); @@ -1308,13 +1314,19 @@ int git_diff_tree_to_tree( if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0) iflag = GIT_ITERATOR_IGNORE_CASE; - DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, &a_opts), iflag, - git_iterator_for_tree(&b, new_tree, &b_opts), iflag - ); + if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 || + (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || + (error = git_iterator_for_tree(&b, new_tree, &b_opts)) < 0 || + (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) + goto out; - if (!error) - *out = diff; + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); return error; } @@ -1337,9 +1349,13 @@ int git_diff_tree_to_index( git_index *index, const git_diff_options *opts) { - git_diff *diff = NULL; git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_CONFLICTS; + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, + b_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator *a = NULL, *b = NULL; + git_diff *diff = NULL; + char *prefix = NULL; bool index_ignore_case = false; int error = 0; @@ -1352,17 +1368,23 @@ int git_diff_tree_to_index( index_ignore_case = index->ignore_case; - DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, &a_opts), iflag, - git_iterator_for_index(&b, repo, index, &b_opts), iflag - ); + if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 || + (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || + (error = git_iterator_for_index(&b, repo, index, &b_opts)) < 0 || + (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) + goto out; /* if index is in case-insensitive order, re-sort deltas to match */ - if (!error && index_ignore_case) + if (index_ignore_case) git_diff__set_ignore_case(diff, true); - if (!error) - *out = diff; + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); return error; } @@ -1373,7 +1395,11 @@ int git_diff_index_to_workdir( git_index *index, const git_diff_options *opts) { + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, + b_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator *a = NULL, *b = NULL; git_diff *diff = NULL; + char *prefix = NULL; int error = 0; assert(out && repo); @@ -1383,20 +1409,24 @@ int git_diff_index_to_workdir( if (!index && (error = diff_load_index(&index, repo)) < 0) return error; - DIFF_FROM_ITERATORS( - git_iterator_for_index(&a, repo, index, &a_opts), - GIT_ITERATOR_INCLUDE_CONFLICTS, + if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_INCLUDE_CONFLICTS, + &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts)) < 0 || + (error = git_iterator_for_index(&a, repo, index, &a_opts)) < 0 || + (error = git_iterator_for_workdir(&b, repo, index, NULL, &b_opts)) < 0 || + (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) + goto out; - git_iterator_for_workdir(&b, repo, index, NULL, &b_opts), - GIT_ITERATOR_DONT_AUTOEXPAND - ); + if ((diff->opts.flags & GIT_DIFF_UPDATE_INDEX) && ((git_diff_generated *)diff)->index_updated) + if ((error = git_index_write(index)) < 0) + goto out; - if (!error && (diff->opts.flags & GIT_DIFF_UPDATE_INDEX) != 0 && - ((git_diff_generated *)diff)->index_updated) - error = git_index_write(index); - - if (!error) - *out = diff; + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); return error; } @@ -1407,24 +1437,33 @@ int git_diff_tree_to_workdir( git_tree *old_tree, const git_diff_options *opts) { + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, + b_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator *a = NULL, *b = NULL; git_diff *diff = NULL; + char *prefix = NULL; git_index *index; - int error = 0; + int error; assert(out && repo); *out = NULL; - if ((error = git_repository_index__weakptr(&index, repo))) - return error; + if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, 0, + &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts) < 0) || + (error = git_repository_index__weakptr(&index, repo)) < 0 || + (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || + (error = git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts)) < 0 || + (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) + goto out; - DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, &a_opts), 0, - git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts), GIT_ITERATOR_DONT_AUTOEXPAND - ); - - if (!error) - *out = diff; + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); return error; } @@ -1468,24 +1507,35 @@ int git_diff_index_to_index( git_index *new_index, const git_diff_options *opts) { - git_diff *diff; - int error = 0; + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, + b_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator *a = NULL, *b = NULL; + git_diff *diff = NULL; + char *prefix = NULL; + int error; assert(out && old_index && new_index); *out = NULL; - DIFF_FROM_ITERATORS( - git_iterator_for_index(&a, repo, old_index, &a_opts), GIT_ITERATOR_DONT_IGNORE_CASE, - git_iterator_for_index(&b, repo, new_index, &b_opts), GIT_ITERATOR_DONT_IGNORE_CASE - ); + if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_DONT_IGNORE_CASE, + &b_opts, GIT_ITERATOR_DONT_IGNORE_CASE, opts) < 0) || + (error = git_iterator_for_index(&a, repo, old_index, &a_opts)) < 0 || + (error = git_iterator_for_index(&b, repo, new_index, &b_opts)) < 0 || + (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) + goto out; /* if index is in case-insensitive order, re-sort deltas to match */ - if (!error && (old_index->ignore_case || new_index->ignore_case)) + if (old_index->ignore_case || new_index->ignore_case) git_diff__set_ignore_case(diff, true); - if (!error) - *out = diff; + *out = diff; + diff = NULL; +out: + git_iterator_free(a); + git_iterator_free(b); + git_diff_free(diff); + git__free(prefix); return error; } diff --git a/src/diff_generate.h b/src/diff_generate.h index 6e669a3d5..5186d552c 100644 --- a/src/diff_generate.h +++ b/src/diff_generate.h @@ -88,7 +88,7 @@ extern int git_diff__oid_for_file( git_diff *diff, const char *path, uint16_t mode, - git_off_t size); + git_object_size_t size); extern int git_diff__oid_for_entry( git_oid *out, @@ -120,7 +120,7 @@ GIT_INLINE(int) git_diff_file__resolve_zero_size( git_odb_free(odb); if (!error) - file->size = (git_off_t)len; + file->size = (git_object_size_t)len; return error; } diff --git a/src/diff_parse.c b/src/diff_parse.c index b4c76a3eb..098e56f9a 100644 --- a/src/diff_parse.c +++ b/src/diff_parse.c @@ -45,7 +45,7 @@ static git_diff_parsed *diff_parsed_alloc(void) diff->base.patch_fn = git_patch_parsed_from_diff; diff->base.free_fn = diff_parsed_free; - if (git_diff_init_options(&diff->base.opts, GIT_DIFF_OPTIONS_VERSION) < 0) { + if (git_diff_options_init(&diff->base.opts, GIT_DIFF_OPTIONS_VERSION) < 0) { git__free(diff); return NULL; } diff --git a/src/diff_print.c b/src/diff_print.c index 8705615a0..369e5c1e6 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -10,7 +10,7 @@ #include "diff.h" #include "diff_file.h" #include "patch_generate.h" -#include "fileops.h" +#include "futils.h" #include "zstream.h" #include "blob.h" #include "delta.h" @@ -48,7 +48,7 @@ static int diff_print_info_init__common( if (!pi->id_strlen) { if (!repo) pi->id_strlen = GIT_ABBREV_DEFAULT; - else if (git_repository__cvar(&pi->id_strlen, repo, GIT_CVAR_ABBREV) < 0) + else if (git_repository__configmap_lookup(&pi->id_strlen, repo, GIT_CONFIGMAP_ABBREV) < 0) return -1; } @@ -269,7 +269,8 @@ static int diff_print_modes( } static int diff_print_oid_range( - git_buf *out, const git_diff_delta *delta, int id_strlen) + git_buf *out, const git_diff_delta *delta, int id_strlen, + bool print_index) { char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; @@ -293,8 +294,9 @@ static int diff_print_oid_range( git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id); if (delta->old_file.mode == delta->new_file.mode) { - git_buf_printf(out, "index %s..%s %o\n", - start_oid, end_oid, delta->old_file.mode); + if (print_index) + git_buf_printf(out, "index %s..%s %o\n", + start_oid, end_oid, delta->old_file.mode); } else { if (delta->old_file.mode == 0) git_buf_printf(out, "new file mode %o\n", delta->new_file.mode); @@ -303,7 +305,8 @@ static int diff_print_oid_range( else diff_print_modes(out, delta); - git_buf_printf(out, "index %s..%s\n", start_oid, end_oid); + if (print_index) + git_buf_printf(out, "index %s..%s\n", start_oid, end_oid); } return git_buf_oom(out) ? -1 : 0; @@ -325,10 +328,10 @@ static int diff_delta_format_with_paths( const char *oldpath, const char *newpath) { - if (git_oid_iszero(&delta->old_file.id)) + if (git_oid_is_zero(&delta->old_file.id)) oldpath = "/dev/null"; - if (git_oid_iszero(&delta->new_file.id)) + if (git_oid_is_zero(&delta->new_file.id)) newpath = "/dev/null"; return git_buf_printf(out, template, oldpath, newpath); @@ -381,8 +384,8 @@ done: static bool delta_is_unchanged(const git_diff_delta *delta) { - if (git_oid_iszero(&delta->old_file.id) && - git_oid_iszero(&delta->new_file.id)) + if (git_oid_is_zero(&delta->old_file.id) && + git_oid_is_zero(&delta->new_file.id)) return true; if (delta->old_file.mode == GIT_FILEMODE_COMMIT || @@ -400,7 +403,8 @@ int git_diff_delta__format_file_header( const git_diff_delta *delta, const char *oldpfx, const char *newpfx, - int id_strlen) + int id_strlen, + bool print_index) { git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT; bool unchanged = delta_is_unchanged(delta); @@ -431,7 +435,8 @@ int git_diff_delta__format_file_header( } if (!unchanged) { - if ((error = diff_print_oid_range(out, delta, id_strlen)) < 0) + if ((error = diff_print_oid_range(out, delta, + id_strlen, print_index)) < 0) goto done; if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) @@ -566,6 +571,7 @@ static int diff_print_patch_file( (pi->flags & GIT_DIFF_FORCE_BINARY); bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY); int id_strlen = pi->id_strlen; + bool print_index = (pi->format != GIT_DIFF_FORMAT_PATCH_ID); if (binary && show_binary) id_strlen = delta->old_file.id_abbrev ? delta->old_file.id_abbrev : @@ -582,7 +588,8 @@ static int diff_print_patch_file( return 0; if ((error = git_diff_delta__format_file_header( - pi->buf, delta, oldpfx, newpfx, id_strlen)) < 0) + pi->buf, delta, oldpfx, newpfx, + id_strlen, print_index)) < 0) return error; pi->line.origin = GIT_DIFF_LINE_FILE_HDR; @@ -670,6 +677,11 @@ int git_diff_print( print_hunk = diff_print_patch_hunk; print_line = diff_print_patch_line; break; + case GIT_DIFF_FORMAT_PATCH_ID: + print_file = diff_print_patch_file; + print_binary = diff_print_patch_binary; + print_line = diff_print_patch_line; + break; case GIT_DIFF_FORMAT_PATCH_HEADER: print_file = diff_print_patch_file; break; diff --git a/src/diff_stats.c b/src/diff_stats.c index a0681712f..19c9da05f 100644 --- a/src/diff_stats.c +++ b/src/diff_stats.c @@ -55,7 +55,7 @@ int git_diff_file_stats__full_to_buf( { const char *old_path = NULL, *new_path = NULL; size_t padding; - git_off_t old_size, new_size; + git_object_size_t old_size, new_size; old_path = delta->old_file.path; new_path = delta->new_file.path; diff --git a/src/diff_tform.c b/src/diff_tform.c index 00ce1cbf6..437144ae2 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -14,7 +14,7 @@ #include "diff.h" #include "diff_generate.h" #include "path.h" -#include "fileops.h" +#include "futils.h" #include "config.h" git_diff_delta *git_diff__delta_dup( @@ -389,7 +389,7 @@ static int apply_splits_and_deletes( if (insert_delete_side_of_split(diff, &onto, delta) < 0) goto on_error; - if (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) + if (diff->new_src == GIT_ITERATOR_WORKDIR) delta->status = GIT_DELTA_UNTRACKED; else delta->status = GIT_DELTA_ADDED; @@ -441,7 +441,7 @@ GIT_INLINE(git_diff_file *) similarity_get_file(git_diff *diff, size_t idx) typedef struct { size_t idx; - git_iterator_type_t src; + git_iterator_t src; git_repository *repo; git_diff_file *file; git_buf data; @@ -460,7 +460,7 @@ static int similarity_init( info->blob = NULL; git_buf_init(&info->data, 0); - if (info->file->size > 0 || info->src == GIT_ITERATOR_TYPE_WORKDIR) + if (info->file->size > 0 || info->src == GIT_ITERATOR_WORKDIR) return 0; return git_diff_file__resolve_zero_size( @@ -475,7 +475,7 @@ static int similarity_sig( int error = 0; git_diff_file *file = info->file; - if (info->src == GIT_ITERATOR_TYPE_WORKDIR) { + if (info->src == GIT_ITERATOR_WORKDIR) { if ((error = git_buf_joinpath( &info->data, git_repository_workdir(info->repo), file->path)) < 0) return error; @@ -510,7 +510,7 @@ static int similarity_sig( if (file->size != git_blob_rawsize(info->blob)) file->size = git_blob_rawsize(info->blob); - sz = (size_t)(git__is_sizet(file->size) ? file->size : -1); + sz = git__is_sizet(file->size) ? (size_t)file->size : (size_t)-1; error = opts->metric->buffer_signature( &cache[info->idx], info->file, @@ -560,14 +560,14 @@ static int similarity_measure( /* if exact match is requested, force calculation of missing OIDs now */ if (exact_match) { - if (git_oid_iszero(&a_file->id) && - diff->old_src == GIT_ITERATOR_TYPE_WORKDIR && + if (git_oid_is_zero(&a_file->id) && + diff->old_src == GIT_ITERATOR_WORKDIR && !git_diff__oid_for_file(&a_file->id, diff, a_file->path, a_file->mode, a_file->size)) a_file->flags |= GIT_DIFF_FLAG_VALID_ID; - if (git_oid_iszero(&b_file->id) && - diff->new_src == GIT_ITERATOR_TYPE_WORKDIR && + if (git_oid_is_zero(&b_file->id) && + diff->new_src == GIT_ITERATOR_WORKDIR && !git_diff__oid_for_file(&b_file->id, diff, b_file->path, b_file->mode, b_file->size)) b_file->flags |= GIT_DIFF_FLAG_VALID_ID; @@ -1013,7 +1013,7 @@ find_best_matches: delta_make_rename(tgt, src, best_match->similarity); - src->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ? + src->status = (diff->new_src == GIT_ITERATOR_WORKDIR) ? GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED; src->nfiles = 1; memset(&src->old_file, 0, sizeof(src->old_file)); diff --git a/src/errors.c b/src/errors.c index afa340936..b34aa3abb 100644 --- a/src/errors.c +++ b/src/errors.c @@ -49,9 +49,17 @@ void git_error_set_oom(void) GIT_GLOBAL->last_error = &g_git_oom_error; } -void git_error_set(int error_class, const char *string, ...) +void git_error_set(int error_class, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + git_error_vset(error_class, fmt, ap); + va_end(ap); +} + +void git_error_vset(int error_class, const char *fmt, va_list ap) { - va_list arglist; #ifdef GIT_WIN32 DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0; #endif @@ -59,11 +67,8 @@ void git_error_set(int error_class, const char *string, ...) git_buf *buf = &GIT_GLOBAL->error_buf; git_buf_clear(buf); - if (string) { - va_start(arglist, string); - git_buf_vprintf(buf, string, arglist); - va_end(arglist); - + if (fmt) { + git_buf_vprintf(buf, fmt, ap); if (error_class == GIT_ERROR_OS) git_buf_PUTS(buf, ": "); } @@ -90,34 +95,25 @@ void git_error_set(int error_class, const char *string, ...) set_error_from_buffer(error_class); } -void git_error_set_str(int error_class, const char *string) +int git_error_set_str(int error_class, const char *string) { git_buf *buf = &GIT_GLOBAL->error_buf; assert(string); - if (!string) - return; + if (!string) { + git_error_set(GIT_ERROR_INVALID, "unspecified caller error"); + return -1; + } git_buf_clear(buf); git_buf_puts(buf, string); - if (!git_buf_oom(buf)) - set_error_from_buffer(error_class); -} -int git_error_set_regex(const regex_t *regex, int error_code) -{ - char error_buf[1024]; + if (git_buf_oom(buf)) + return -1; - assert(error_code); - - regerror(error_code, regex, error_buf, sizeof(error_buf)); - git_error_set_str(GIT_ERROR_REGEX, error_buf); - - if (error_code == REG_NOMATCH) - return GIT_ENOTFOUND; - - return GIT_EINVALIDSPEC; + set_error_from_buffer(error_class); + return 0; } void git_error_clear(void) diff --git a/src/errors.h b/src/errors.h new file mode 100644 index 000000000..a2f60f752 --- /dev/null +++ b/src/errors.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_errors_h__ +#define INCLUDE_errors_h__ + +#include "common.h" + +/* + * Set the error message for this thread, formatting as needed. + */ +void git_error_set(int error_class, const char *fmt, ...) GIT_FORMAT_PRINTF(2, 3); +void git_error_vset(int error_class, const char *fmt, va_list ap); + +/** + * Set error message for user callback if needed. + * + * If the error code in non-zero and no error message is set, this + * sets a generic error message. + * + * @return This always returns the `error_code` parameter. + */ +GIT_INLINE(int) git_error_set_after_callback_function( + int error_code, const char *action) +{ + if (error_code) { + const git_error *e = git_error_last(); + if (!e || !e->message) + git_error_set(e ? e->klass : GIT_ERROR_CALLBACK, + "%s callback returned %d", action, error_code); + } + return error_code; +} + +#ifdef GIT_WIN32 +#define git_error_set_after_callback(code) \ + git_error_set_after_callback_function((code), __FUNCTION__) +#else +#define git_error_set_after_callback(code) \ + git_error_set_after_callback_function((code), __func__) +#endif + +/** + * Gets the system error code for this thread. + */ +int git_error_system_last(void); + +/** + * Sets the system error code for this thread. + */ +void git_error_system_set(int code); + +/** + * Structure to preserve libgit2 error state + */ +typedef struct { + int error_code; + unsigned int oom : 1; + git_error error_msg; +} git_error_state; + +/** + * Capture current error state to restore later, returning error code. + * If `error_code` is zero, this does not clear the current error state. + * You must either restore this error state, or free it. + */ +extern int git_error_state_capture(git_error_state *state, int error_code); + +/** + * Restore error state to a previous value, returning saved error code. + */ +extern int git_error_state_restore(git_error_state *state); + +/** Free an error state. */ +extern void git_error_state_free(git_error_state *state); + +#endif diff --git a/src/features.h.in b/src/features.h.in index 694a61c02..e000de5e0 100644 --- a/src/features.h.in +++ b/src/features.h.in @@ -15,14 +15,21 @@ #cmakedefine GIT_USE_STAT_MTIMESPEC 1 #cmakedefine GIT_USE_STAT_MTIME_NSEC 1 #cmakedefine GIT_USE_FUTIMENS 1 -#cmakedefine GIT_USE_REGCOMP_L 1 + +#cmakedefine GIT_REGEX_REGCOMP_L +#cmakedefine GIT_REGEX_REGCOMP +#cmakedefine GIT_REGEX_PCRE +#cmakedefine GIT_REGEX_PCRE2 +#cmakedefine GIT_REGEX_BUILTIN 1 #cmakedefine GIT_SSH 1 #cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1 +#cmakedefine GIT_NTLM 1 #cmakedefine GIT_GSSAPI 1 -#cmakedefine GIT_WINHTTP 1 +#cmakedefine GIT_GSSFRAMEWORK 1 +#cmakedefine GIT_WINHTTP 1 #cmakedefine GIT_HTTPS 1 #cmakedefine GIT_OPENSSL 1 #cmakedefine GIT_SECURE_TRANSPORT 1 diff --git a/src/fetch.c b/src/fetch.c index d29341b94..f2f313192 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -134,7 +134,7 @@ int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts) int git_fetch_download_pack(git_remote *remote, const git_remote_callbacks *callbacks) { git_transport *t = remote->transport; - git_transfer_progress_cb progress = NULL; + git_indexer_progress_cb progress = NULL; void *payload = NULL; if (!remote->need_pack) @@ -148,9 +148,14 @@ int git_fetch_download_pack(git_remote *remote, const git_remote_callbacks *call return t->download_pack(t, remote->repo, &remote->stats, progress, payload); } -int git_fetch_init_options(git_fetch_options *opts, unsigned int version) +int git_fetch_options_init(git_fetch_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_fetch_options, GIT_FETCH_OPTIONS_INIT); return 0; } + +int git_fetch_init_options(git_fetch_options *opts, unsigned int version) +{ + return git_fetch_options_init(opts, version); +} diff --git a/src/fetchhead.c b/src/fetchhead.c index bdded029a..ea610f39a 100644 --- a/src/fetchhead.c +++ b/src/fetchhead.c @@ -11,9 +11,10 @@ #include "git2/oid.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" #include "filebuf.h" #include "refs.h" +#include "net.h" #include "repository.h" int git_fetchhead_ref_cmp(const void *a, const void *b) @@ -36,6 +37,33 @@ int git_fetchhead_ref_cmp(const void *a, const void *b) return 0; } +static char *sanitized_remote_url(const char *remote_url) +{ + git_net_url url = GIT_NET_URL_INIT; + char *sanitized = NULL; + int error; + + if (git_net_url_parse(&url, remote_url) == 0) { + git_buf buf = GIT_BUF_INIT; + + git__free(url.username); + git__free(url.password); + url.username = url.password = NULL; + + if ((error = git_net_url_fmt(&buf, &url)) < 0) + goto fallback; + + sanitized = git_buf_detach(&buf); + } + +fallback: + if (!sanitized) + sanitized = git__strdup(remote_url); + + git_net_url_dispose(&url); + return sanitized; +} + int git_fetchhead_ref_create( git_fetchhead_ref **out, git_oid *oid, @@ -57,11 +85,15 @@ int git_fetchhead_ref_create( git_oid_cpy(&fetchhead_ref->oid, oid); fetchhead_ref->is_merge = is_merge; - if (ref_name) + if (ref_name) { fetchhead_ref->ref_name = git__strdup(ref_name); + GIT_ERROR_CHECK_ALLOC(fetchhead_ref->ref_name); + } - if (remote_url) - fetchhead_ref->remote_url = git__strdup(remote_url); + if (remote_url) { + fetchhead_ref->remote_url = sanitized_remote_url(remote_url); + GIT_ERROR_CHECK_ALLOC(fetchhead_ref->remote_url); + } *out = fetchhead_ref; diff --git a/src/filebuf.c b/src/filebuf.c index 8a70bcd66..e12a9eabf 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -7,7 +7,7 @@ #include "filebuf.h" -#include "fileops.h" +#include "futils.h" static const size_t WRITE_BUFFER_SIZE = (4096 * 2); @@ -44,18 +44,14 @@ static int verify_last_error(git_filebuf *file) static int lock_file(git_filebuf *file, int flags, mode_t mode) { if (git_path_exists(file->path_lock) == true) { - if (flags & GIT_FILEBUF_FORCE) - p_unlink(file->path_lock); - else { - git_error_clear(); /* actual OS error code just confuses */ - git_error_set(GIT_ERROR_OS, - "failed to lock file '%s' for writing", file->path_lock); - return GIT_ELOCKED; - } + git_error_clear(); /* actual OS error code just confuses */ + git_error_set(GIT_ERROR_OS, + "failed to lock file '%s' for writing", file->path_lock); + return GIT_ELOCKED; } /* create path to the file buffer is required */ - if (flags & GIT_FILEBUF_FORCE) { + if (flags & GIT_FILEBUF_CREATE_LEADING_DIRS) { /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */ file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode); } else { diff --git a/src/filebuf.h b/src/filebuf.h index f51ff230f..9d53bc307 100644 --- a/src/filebuf.h +++ b/src/filebuf.h @@ -9,7 +9,7 @@ #include "common.h" -#include "fileops.h" +#include "futils.h" #include "hash.h" #include @@ -19,7 +19,7 @@ #define GIT_FILEBUF_HASH_CONTENTS (1 << 0) #define GIT_FILEBUF_APPEND (1 << 2) -#define GIT_FILEBUF_FORCE (1 << 3) +#define GIT_FILEBUF_CREATE_LEADING_DIRS (1 << 3) #define GIT_FILEBUF_TEMPORARY (1 << 4) #define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5) #define GIT_FILEBUF_FSYNC (1 << 6) diff --git a/src/filter.c b/src/filter.c index 33ddfe2a5..bb9d66ad6 100644 --- a/src/filter.c +++ b/src/filter.c @@ -8,7 +8,7 @@ #include "filter.h" #include "common.h" -#include "fileops.h" +#include "futils.h" #include "hash.h" #include "repository.h" #include "global.h" @@ -385,7 +385,7 @@ uint16_t git_filter_source_filemode(const git_filter_source *src) const git_oid *git_filter_source_id(const git_filter_source *src) { - return git_oid_iszero(&src->oid) ? NULL : &src->oid; + return git_oid_is_zero(&src->oid) ? NULL : &src->oid; } git_filter_mode_t git_filter_source_mode(const git_filter_source *src) @@ -428,13 +428,21 @@ static int filter_list_check_attributes( git_filter_def *fdef, const git_filter_source *src) { - int error; - size_t i; const char **strs = git__calloc(fdef->nattrs, sizeof(const char *)); + uint32_t flags = 0; + size_t i; + int error; + GIT_ERROR_CHECK_ALLOC(strs); + if ((src->flags & GIT_FILTER_NO_SYSTEM_ATTRIBUTES) != 0) + flags |= GIT_ATTR_CHECK_NO_SYSTEM; + + if ((src->flags & GIT_FILTER_ATTRIBUTES_FROM_HEAD) != 0) + flags |= GIT_ATTR_CHECK_INCLUDE_HEAD; + error = git_attr_get_many_with_session( - strs, repo, attr_session, 0, src->path, fdef->nattrs, fdef->attrs); + strs, repo, attr_session, flags, src->path, fdef->nattrs, fdef->attrs); /* if no values were found but no matches are needed, it's okay! */ if (error == GIT_ENOTFOUND && !fdef->nmatches) { @@ -445,7 +453,7 @@ static int filter_list_check_attributes( for (i = 0; !error && i < fdef->nattrs; ++i) { const char *want = fdef->attrs[fdef->nattrs + i]; - git_attr_t want_type, found_type; + git_attr_value_t want_type, found_type; if (!want) continue; @@ -455,7 +463,7 @@ static int filter_list_check_attributes( if (want_type != found_type) error = GIT_ENOTFOUND; - else if (want_type == GIT_ATTR_VALUE_T && + else if (want_type == GIT_ATTR_VALUE_STRING && strcmp(want, strs[i]) && strcmp(want, "*")) error = GIT_ENOTFOUND; @@ -756,7 +764,7 @@ int git_filter_list_apply_to_file( static int buf_from_blob(git_buf *out, git_blob *blob) { - git_off_t rawsize = git_blob_rawsize(blob); + git_object_size_t rawsize = git_blob_rawsize(blob); if (!git__is_sizet(rawsize)) { git_error_set(GIT_ERROR_OS, "blob is too large to filter"); diff --git a/src/fnmatch.c b/src/fnmatch.c deleted file mode 100644 index 3cc2a27ba..000000000 --- a/src/fnmatch.c +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -/* - * This file contains code originally derrived from OpenBSD fnmatch.c - * - * Copyright (c) 1989, 1993, 1994 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Guido van Rossum. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* - * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. - * Compares a filename or pathname to a pattern. - */ - -#include "fnmatch.h" - -#include -#include -#include - -#define EOS '\0' - -#define RANGE_MATCH 1 -#define RANGE_NOMATCH 0 -#define RANGE_ERROR (-1) - -static int rangematch(const char *, char, int, char **); - -static int -p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs) -{ - const char *stringstart; - char *newp; - char c, test; - int recurs_flags = flags & ~FNM_PERIOD; - - if (recurs-- == 0) - return FNM_NORES; - - for (stringstart = string;;) - switch (c = *pattern++) { - case EOS: - if ((flags & FNM_LEADING_DIR) && *string == '/') - return (0); - return (*string == EOS ? 0 : FNM_NOMATCH); - case '?': - if (*string == EOS) - return (FNM_NOMATCH); - if (*string == '/' && (flags & FNM_PATHNAME)) - return (FNM_NOMATCH); - if (*string == '.' && (flags & FNM_PERIOD) && - (string == stringstart || - ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); - ++string; - break; - case '*': - c = *pattern; - - /* Let '**' override PATHNAME match for this segment. - * It will be restored if/when we recurse below. - */ - if (c == '*') { - c = *++pattern; - /* star-star-slash is at the end, match by default */ - if (c == EOS) - return 0; - /* Double-star must be at end or between slashes */ - if (c != '/') - return (FNM_NOMATCH); - - c = *++pattern; - do { - int e = p_fnmatchx(pattern, string, recurs_flags, recurs); - if (e != FNM_NOMATCH) - return e; - string = strchr(string, '/'); - } while (string++); - - /* If we get here, we didn't find a match */ - return FNM_NOMATCH; - } - - if (*string == '.' && (flags & FNM_PERIOD) && - (string == stringstart || - ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); - - /* Optimize for pattern with * at end or before /. */ - if (c == EOS) { - if (flags & FNM_PATHNAME) - return ((flags & FNM_LEADING_DIR) || - strchr(string, '/') == NULL ? - 0 : FNM_NOMATCH); - else - return (0); - } else if (c == '/' && (flags & FNM_PATHNAME)) { - if ((string = strchr(string, '/')) == NULL) - return (FNM_NOMATCH); - break; - } - - /* General case, use recursion. */ - while ((test = *string) != EOS) { - int e; - - e = p_fnmatchx(pattern, string, recurs_flags, recurs); - if (e != FNM_NOMATCH) - return e; - if (test == '/' && (flags & FNM_PATHNAME)) - break; - ++string; - } - return (FNM_NOMATCH); - case '[': - if (*string == EOS) - return (FNM_NOMATCH); - if (*string == '/' && (flags & FNM_PATHNAME)) - return (FNM_NOMATCH); - if (*string == '.' && (flags & FNM_PERIOD) && - (string == stringstart || - ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); - - switch (rangematch(pattern, *string, flags, &newp)) { - case RANGE_ERROR: - /* not a good range, treat as normal text */ - goto normal; - case RANGE_MATCH: - pattern = newp; - break; - case RANGE_NOMATCH: - return (FNM_NOMATCH); - } - ++string; - break; - case '\\': - if (!(flags & FNM_NOESCAPE)) { - if ((c = *pattern++) == EOS) { - c = '\\'; - --pattern; - } - } - /* FALLTHROUGH */ - default: - normal: - if (c != *string && !((flags & FNM_CASEFOLD) && - (git__tolower((unsigned char)c) == - git__tolower((unsigned char)*string)))) - return (FNM_NOMATCH); - ++string; - break; - } - /* NOTREACHED */ -} - -static int -rangematch(const char *pattern, char test, int flags, char **newp) -{ - int negate, ok; - char c, c2; - - /* - * A bracket expression starting with an unquoted circumflex - * character produces unspecified results (IEEE 1003.2-1992, - * 3.13.2). This implementation treats it like '!', for - * consistency with the regular expression syntax. - * J.T. Conklin (conklin@ngai.kaleida.com) - */ - if ((negate = (*pattern == '!' || *pattern == '^')) != 0) - ++pattern; - - if (flags & FNM_CASEFOLD) - test = (char)git__tolower((unsigned char)test); - - /* - * A right bracket shall lose its special meaning and represent - * itself in a bracket expression if it occurs first in the list. - * -- POSIX.2 2.8.3.2 - */ - ok = 0; - c = *pattern++; - do { - if (c == '\\' && !(flags & FNM_NOESCAPE)) - c = *pattern++; - if (c == EOS) - return (RANGE_ERROR); - if (c == '/' && (flags & FNM_PATHNAME)) - return (RANGE_NOMATCH); - if ((flags & FNM_CASEFOLD)) - c = (char)git__tolower((unsigned char)c); - if (*pattern == '-' - && (c2 = *(pattern+1)) != EOS && c2 != ']') { - pattern += 2; - if (c2 == '\\' && !(flags & FNM_NOESCAPE)) - c2 = *pattern++; - if (c2 == EOS) - return (RANGE_ERROR); - if (flags & FNM_CASEFOLD) - c2 = (char)git__tolower((unsigned char)c2); - if (c <= test && test <= c2) - ok = 1; - } else if (c == test) - ok = 1; - } while ((c = *pattern++) != ']'); - - *newp = (char *)pattern; - return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); -} - -int -p_fnmatch(const char *pattern, const char *string, int flags) -{ - return p_fnmatchx(pattern, string, flags, 64); -} - diff --git a/src/fnmatch.h b/src/fnmatch.h deleted file mode 100644 index ddaae15bb..000000000 --- a/src/fnmatch.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -#ifndef INCLUDE_fnmatch_h__ -#define INCLUDE_fnmatch_h__ - -#include "common.h" - -#define FNM_NOMATCH 1 /* Match failed. */ -#define FNM_NOSYS 2 /* Function not supported (unused). */ -#define FNM_NORES 3 /* Out of resources */ - -#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ -#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ -#define FNM_PERIOD 0x04 /* Period must be matched by period. */ -#define FNM_LEADING_DIR 0x08 /* Ignore / after Imatch. */ -#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ - -#define FNM_IGNORECASE FNM_CASEFOLD -#define FNM_FILE_NAME FNM_PATHNAME - -extern int p_fnmatch(const char *pattern, const char *string, int flags); - -#endif diff --git a/src/fileops.c b/src/futils.c similarity index 97% rename from src/fileops.c rename to src/futils.c index 877e9cdc5..a7c360a1c 100644 --- a/src/fileops.c +++ b/src/futils.c @@ -5,7 +5,7 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "fileops.h" +#include "futils.h" #include "global.h" #include "strmap.h" @@ -112,7 +112,7 @@ int git_futils_truncate(const char *path, int mode) return 0; } -git_off_t git_futils_filesize(git_file fd) +int git_futils_filesize(uint64_t *out, git_file fd) { struct stat sb; @@ -121,7 +121,13 @@ git_off_t git_futils_filesize(git_file fd) return -1; } - return sb.st_size; + if (sb.st_size < 0) { + git_error_set(GIT_ERROR_INVALID, "invalid file size"); + return -1; + } + + *out = sb.st_size; + return 0; } mode_t git_futils_canonical_mode(mode_t raw_mode) @@ -301,7 +307,7 @@ int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmod return 0; } -int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len) +int git_futils_mmap_ro(git_map *out, git_file fd, off64_t begin, size_t len) { return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin); } @@ -309,16 +315,14 @@ int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len) int git_futils_mmap_ro_file(git_map *out, const char *path) { git_file fd = git_futils_open_ro(path); - git_off_t len; + uint64_t len; int result; if (fd < 0) return fd; - if ((len = git_futils_filesize(fd)) < 0) { - result = -1; + if ((result = git_futils_filesize(&len, fd)) < 0) goto out; - } if (!git__is_sizet(len)) { git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path); @@ -476,6 +480,7 @@ int git_futils_mkdir( break; } else if (errno != ENOENT) { git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr); + error = -1; goto done; } @@ -495,7 +500,9 @@ int git_futils_mkdir( * equal to length of the root path). The path may be less than the * root path length on Windows, where `C:` == `C:/`. */ - if ((len == 1 && parent_path.ptr[0] == '.') || len <= root_len) { + if ((len == 1 && parent_path.ptr[0] == '.') || + (len == 1 && parent_path.ptr[0] == '/') || + len <= root_len) { relative = make_path.ptr; break; } @@ -636,15 +643,12 @@ retry_lstat: size_t alloc_size; GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1); - if (!git__is_uint32(alloc_size)) - return -1; - cache_path = git_pool_malloc(opts->pool, (uint32_t)alloc_size); + cache_path = git_pool_malloc(opts->pool, alloc_size); GIT_ERROR_CHECK_ALLOC(cache_path); memcpy(cache_path, make_path.ptr, make_path.size + 1); - git_strmap_insert(opts->dir_map, cache_path, cache_path, &error); - if (error < 0) + if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0) goto done; } } @@ -1106,7 +1110,7 @@ int git_futils_filestamp_check( #if defined(GIT_USE_NSEC) stamp->mtime.tv_nsec == st.st_mtime_nsec && #endif - stamp->size == (git_off_t)st.st_size && + stamp->size == (uint64_t)st.st_size && stamp->ino == (unsigned int)st.st_ino) return 0; @@ -1114,7 +1118,7 @@ int git_futils_filestamp_check( #if defined(GIT_USE_NSEC) stamp->mtime.tv_nsec = st.st_mtime_nsec; #endif - stamp->size = (git_off_t)st.st_size; + stamp->size = (uint64_t)st.st_size; stamp->ino = (unsigned int)st.st_ino; return 1; @@ -1142,7 +1146,7 @@ void git_futils_filestamp_set_from_stat( #else stamp->mtime.tv_nsec = 0; #endif - stamp->size = (git_off_t)st->st_size; + stamp->size = (uint64_t)st->st_size; stamp->ino = (unsigned int)st->st_ino; } else { memset(stamp, 0, sizeof(*stamp)); diff --git a/src/fileops.h b/src/futils.h similarity index 98% rename from src/fileops.h rename to src/futils.h index 85131f834..3d5664679 100644 --- a/src/fileops.h +++ b/src/futils.h @@ -4,8 +4,8 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_fileops_h__ -#define INCLUDE_fileops_h__ +#ifndef INCLUDE_futils_h__ +#define INCLUDE_futils_h__ #include "common.h" @@ -255,7 +255,7 @@ extern int git_futils_truncate(const char *path, int mode); /** * Get the filesize in bytes of a file */ -extern git_off_t git_futils_filesize(git_file fd); +extern int git_futils_filesize(uint64_t *out, git_file fd); #define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0111) != 0) #define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644) @@ -290,7 +290,7 @@ extern mode_t git_futils_canonical_mode(mode_t raw_mode); extern int git_futils_mmap_ro( git_map *out, git_file fd, - git_off_t begin, + off64_t begin, size_t len); /** @@ -330,7 +330,7 @@ extern int git_futils_fake_symlink(const char *new, const char *old); */ typedef struct { struct timespec mtime; - git_off_t size; + uint64_t size; unsigned int ino; } git_futils_filestamp; diff --git a/src/global.c b/src/global.c index 418c036c1..5af35aa62 100644 --- a/src/global.c +++ b/src/global.c @@ -141,14 +141,21 @@ static void shutdown_common(void) */ #if defined(GIT_THREADS) && defined(GIT_WIN32) -static DWORD _tls_index; +static DWORD _fls_index; static volatile LONG _mutex = 0; +static void WINAPI fls_free(void *st) +{ + git__global_state_cleanup(st); + git__free(st); +} + static int synchronized_threads_init(void) { int error; - _tls_index = TlsAlloc(); + if ((_fls_index = FlsAlloc(fls_free)) == FLS_OUT_OF_INDEXES) + return -1; git_threads_init(); @@ -190,9 +197,7 @@ int git_libgit2_shutdown(void) if ((ret = git_atomic_dec(&git__n_inits)) == 0) { shutdown_common(); - git__free_tls_data(); - - TlsFree(_tls_index); + FlsFree(_fls_index); git_mutex_free(&git__mwindow_mutex); #if defined(GIT_MSVC_CRTDBG) @@ -213,7 +218,7 @@ git_global_st *git__global_state(void) assert(git_atomic_get(&git__n_inits) > 0); - if ((ptr = TlsGetValue(_tls_index)) != NULL) + if ((ptr = FlsGetValue(_fls_index)) != NULL) return ptr; ptr = git__calloc(1, sizeof(git_global_st)); @@ -222,43 +227,10 @@ git_global_st *git__global_state(void) git_buf_init(&ptr->error_buf, 0); - TlsSetValue(_tls_index, ptr); + FlsSetValue(_fls_index, ptr); return ptr; } -/** - * Free the TLS data associated with this thread. - * This should only be used by the thread as it - * is exiting. - */ -void git__free_tls_data(void) -{ - void *ptr = TlsGetValue(_tls_index); - if (!ptr) - return; - - git__global_state_cleanup(ptr); - git__free(ptr); - TlsSetValue(_tls_index, NULL); -} - -BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved) -{ - GIT_UNUSED(hInstDll); - GIT_UNUSED(lpvReserved); - - /* This is how Windows lets us know our thread is being shut down */ - if (fdwReason == DLL_THREAD_DETACH) { - git__free_tls_data(); - } - - /* - * Windows pays attention to this during library loading. We don't do anything - * so we trivially succeed. - */ - return TRUE; -} - #elif defined(GIT_THREADS) && defined(_POSIX_THREADS) static pthread_key_t _tls_key; diff --git a/src/global.h b/src/global.h index 3c0559c68..db41dad1f 100644 --- a/src/global.h +++ b/src/global.h @@ -35,8 +35,6 @@ typedef void (*git_global_shutdown_fn)(void); extern void git__on_shutdown(git_global_shutdown_fn callback); -extern void git__free_tls_data(void); - extern const char *git_libgit2__user_agent(void); extern const char *git_libgit2__ssl_ciphers(void); diff --git a/src/hash.c b/src/hash.c index cc6676d4d..405c46a9a 100644 --- a/src/hash.c +++ b/src/hash.c @@ -7,6 +7,67 @@ #include "hash.h" +int git_hash_global_init(void) +{ + return git_hash_sha1_global_init(); +} + +int git_hash_ctx_init(git_hash_ctx *ctx) +{ + int error; + + if ((error = git_hash_sha1_ctx_init(&ctx->sha1)) < 0) + return error; + + ctx->algo = GIT_HASH_ALGO_SHA1; + + return 0; +} + +void git_hash_ctx_cleanup(git_hash_ctx *ctx) +{ + switch (ctx->algo) { + case GIT_HASH_ALGO_SHA1: + git_hash_sha1_ctx_cleanup(&ctx->sha1); + return; + default: + assert(0); + } +} + +int git_hash_init(git_hash_ctx *ctx) +{ + switch (ctx->algo) { + case GIT_HASH_ALGO_SHA1: + return git_hash_sha1_init(&ctx->sha1); + default: + assert(0); + return -1; + } +} + +int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +{ + switch (ctx->algo) { + case GIT_HASH_ALGO_SHA1: + return git_hash_sha1_update(&ctx->sha1, data, len); + default: + assert(0); + return -1; + } +} + +int git_hash_final(git_oid *out, git_hash_ctx *ctx) +{ + switch (ctx->algo) { + case GIT_HASH_ALGO_SHA1: + return git_hash_sha1_final(out, &ctx->sha1); + default: + assert(0); + return -1; + } +} + int git_hash_buf(git_oid *out, const void *data, size_t len) { git_hash_ctx ctx; diff --git a/src/hash.h b/src/hash.h index 0502e352e..017bb286c 100644 --- a/src/hash.h +++ b/src/hash.h @@ -4,6 +4,7 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ + #ifndef INCLUDE_hash_h__ #define INCLUDE_hash_h__ @@ -11,33 +12,30 @@ #include "git2/oid.h" -typedef struct git_hash_prov git_hash_prov; -typedef struct git_hash_ctx git_hash_ctx; - -int git_hash_ctx_init(git_hash_ctx *ctx); -void git_hash_ctx_cleanup(git_hash_ctx *ctx); - -#if defined(GIT_SHA1_COLLISIONDETECT) -# include "hash/hash_collisiondetect.h" -#elif defined(GIT_SHA1_COMMON_CRYPTO) -# include "hash/hash_common_crypto.h" -#elif defined(GIT_SHA1_OPENSSL) -# include "hash/hash_openssl.h" -#elif defined(GIT_SHA1_WIN32) -# include "hash/hash_win32.h" -#elif defined(GIT_SHA1_MBEDTLS) -# include "hash/hash_mbedtls.h" -#else -# include "hash/hash_generic.h" -#endif - -int git_hash_global_init(void); - typedef struct { void *data; size_t len; } git_buf_vec; +typedef enum { + GIT_HASH_ALGO_UNKNOWN = 0, + GIT_HASH_ALGO_SHA1, +} git_hash_algo_t; + +#include "hash/sha1.h" + +typedef struct git_hash_ctx { + union { + git_hash_sha1_ctx sha1; + }; + git_hash_algo_t algo; +} git_hash_ctx; + +int git_hash_global_init(void); + +int git_hash_ctx_init(git_hash_ctx *ctx); +void git_hash_ctx_cleanup(git_hash_ctx *ctx); + int git_hash_init(git_hash_ctx *c); int git_hash_update(git_hash_ctx *c, const void *data, size_t len); int git_hash_final(git_oid *out, git_hash_ctx *c); diff --git a/src/hash/sha1.h b/src/hash/sha1.h new file mode 100644 index 000000000..fb8d62f80 --- /dev/null +++ b/src/hash/sha1.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_h__ +#define INCLUDE_hash_sha1_h__ + +#include "common.h" + +typedef struct git_hash_sha1_ctx git_hash_sha1_ctx; + +#if defined(GIT_SHA1_COLLISIONDETECT) +# include "sha1/collisiondetect.h" +#elif defined(GIT_SHA1_COMMON_CRYPTO) +# include "sha1/common_crypto.h" +#elif defined(GIT_SHA1_OPENSSL) +# include "sha1/openssl.h" +#elif defined(GIT_SHA1_WIN32) +# include "sha1/win32.h" +#elif defined(GIT_SHA1_MBEDTLS) +# include "sha1/mbedtls.h" +#else +# include "sha1/generic.h" +#endif + +int git_hash_sha1_global_init(void); + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx); +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx); + +int git_hash_sha1_init(git_hash_sha1_ctx *c); +int git_hash_sha1_update(git_hash_sha1_ctx *c, const void *data, size_t len); +int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *c); + +#endif diff --git a/src/hash/hash_collisiondetect.h b/src/hash/sha1/collisiondetect.c similarity index 50% rename from src/hash/hash_collisiondetect.h rename to src/hash/sha1/collisiondetect.c index 743209802..e6a126780 100644 --- a/src/hash/hash_collisiondetect.h +++ b/src/hash/sha1/collisiondetect.c @@ -5,39 +5,38 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hash_hash_collisiondetect_h__ -#define INCLUDE_hash_hash_collisiondetect_h__ +#include "collisiondetect.h" -#include "hash.h" -#include "sha1dc/sha1.h" - -struct git_hash_ctx { - SHA1_CTX c; -}; - -#define git_hash_ctx_init(ctx) git_hash_init(ctx) -#define git_hash_ctx_cleanup(ctx) - -GIT_INLINE(int) git_hash_global_init(void) +int git_hash_sha1_global_init(void) { return 0; } -GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx) +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) { assert(ctx); SHA1DCInit(&ctx->c); return 0; } -GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) { assert(ctx); SHA1DCUpdate(&ctx->c, data, len); return 0; } -GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx) +int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *ctx) { assert(ctx); if (SHA1DCFinal(out->id, &ctx->c)) { @@ -47,5 +46,3 @@ GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx) return 0; } - -#endif diff --git a/src/sha1_lookup.h b/src/hash/sha1/collisiondetect.h similarity index 50% rename from src/sha1_lookup.h rename to src/hash/sha1/collisiondetect.h index 841ea5b22..eb88e86c1 100644 --- a/src/sha1_lookup.h +++ b/src/hash/sha1/collisiondetect.h @@ -4,16 +4,16 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_sha1_lookup_h__ -#define INCLUDE_sha1_lookup_h__ -#include "common.h" +#ifndef INCLUDE_hash_sha1_collisiondetect_h__ +#define INCLUDE_hash_sha1_collisiondetect_h__ -#include +#include "hash/sha1.h" -int sha1_position(const void *table, - size_t stride, - unsigned lo, unsigned hi, - const unsigned char *key); +#include "sha1dc/sha1.h" + +struct git_hash_sha1_ctx { + SHA1_CTX c; +}; #endif diff --git a/src/hash/hash_common_crypto.h b/src/hash/sha1/common_crypto.c similarity index 54% rename from src/hash/hash_common_crypto.h rename to src/hash/sha1/common_crypto.c index ce352a633..0449a3c9d 100644 --- a/src/hash/hash_common_crypto.h +++ b/src/hash/sha1/common_crypto.c @@ -5,35 +5,33 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hash_hash_common_crypto_h__ -#define INCLUDE_hash_hash_common_crypto_h__ - -#include "hash.h" - -#include - -struct git_hash_ctx { - CC_SHA1_CTX c; -}; +#include "common_crypto.h" #define CC_LONG_MAX ((CC_LONG)-1) -#define git_hash_ctx_init(ctx) git_hash_init(ctx) -#define git_hash_ctx_cleanup(ctx) - -GIT_INLINE(int) git_hash_global_init(void) +int git_hash_sha1_global_init(void) { return 0; } -GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx) +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) { assert(ctx); CC_SHA1_Init(&ctx->c); return 0; } -GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *_data, size_t len) +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) { const unsigned char *data = _data; @@ -51,11 +49,9 @@ GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *_data, size_t len return 0; } -GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx) +int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *ctx) { assert(ctx); CC_SHA1_Final(out->id, &ctx->c); return 0; } - -#endif diff --git a/src/hash/sha1/common_crypto.h b/src/hash/sha1/common_crypto.h new file mode 100644 index 000000000..a5fcfb33e --- /dev/null +++ b/src/hash/sha1/common_crypto.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_common_crypto_h__ +#define INCLUDE_hash_sha1_common_crypto_h__ + +#include "hash/sha1.h" + +#include + +struct git_hash_sha1_ctx { + CC_SHA1_CTX c; +}; + +#endif diff --git a/src/hash/hash_generic.c b/src/hash/sha1/generic.c similarity index 93% rename from src/hash/hash_generic.c rename to src/hash/sha1/generic.c index 7b33b6194..607fe3a43 100644 --- a/src/hash/hash_generic.c +++ b/src/hash/sha1/generic.c @@ -5,9 +5,7 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "hash_generic.h" - -#include "hash.h" +#include "generic.h" #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) @@ -113,7 +111,7 @@ #define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E ) #define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) -static void hash__block(git_hash_ctx *ctx, const unsigned int *data) +static void hash__block(git_hash_sha1_ctx *ctx, const unsigned int *data) { unsigned int A,B,C,D,E; unsigned int array[16]; @@ -221,7 +219,22 @@ static void hash__block(git_hash_ctx *ctx, const unsigned int *data) ctx->H[4] += E; } -int git_hash_init(git_hash_ctx *ctx) +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) { ctx->size = 0; @@ -235,7 +248,7 @@ int git_hash_init(git_hash_ctx *ctx) return 0; } -int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) { unsigned int lenW = ctx->size & 63; @@ -265,7 +278,7 @@ int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) return 0; } -int git_hash_final(git_oid *out, git_hash_ctx *ctx) +int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *ctx) { static const unsigned char pad[64] = { 0x80 }; unsigned int padlen[2]; @@ -276,8 +289,8 @@ int git_hash_final(git_oid *out, git_hash_ctx *ctx) padlen[1] = htonl((uint32_t)(ctx->size << 3)); i = ctx->size & 63; - git_hash_update(ctx, pad, 1+ (63 & (55 - i))); - git_hash_update(ctx, padlen, 8); + git_hash_sha1_update(ctx, pad, 1+ (63 & (55 - i))); + git_hash_sha1_update(ctx, padlen, 8); /* Output hash */ for (i = 0; i < 5; i++) @@ -285,4 +298,3 @@ int git_hash_final(git_oid *out, git_hash_ctx *ctx) return 0; } - diff --git a/src/hash/hash_generic.h b/src/hash/sha1/generic.h similarity index 51% rename from src/hash/hash_generic.h rename to src/hash/sha1/generic.h index fb0009ccf..e4cc6026b 100644 --- a/src/hash/hash_generic.h +++ b/src/hash/sha1/generic.h @@ -5,25 +5,15 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hash_hash_generic_h__ -#define INCLUDE_hash_hash_generic_h__ +#ifndef INCLUDE_hash_sha1_generic_h__ +#define INCLUDE_hash_sha1_generic_h__ -#include "common.h" +#include "hash/sha1.h" -#include "hash.h" - -struct git_hash_ctx { +struct git_hash_sha1_ctx { unsigned long long size; unsigned int H[5]; unsigned int W[16]; }; -#define git_hash_ctx_init(ctx) git_hash_init(ctx) -#define git_hash_ctx_cleanup(ctx) - -GIT_INLINE(int) git_hash_global_init(void) -{ - return 0; -} - #endif diff --git a/src/hash/hash_mbedtls.c b/src/hash/sha1/mbedtls.c similarity index 56% rename from src/hash/hash_mbedtls.c rename to src/hash/sha1/mbedtls.c index a19d76308..e44343fcf 100644 --- a/src/hash/hash_mbedtls.c +++ b/src/hash/sha1/mbedtls.c @@ -5,17 +5,25 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "common.h" -#include "hash.h" -#include "hash/hash_mbedtls.h" +#include "mbedtls.h" -void git_hash_ctx_cleanup(git_hash_ctx *ctx) +int git_hash_sha1_global_init(void) +{ + return 0; +} + +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) { assert(ctx); mbedtls_sha1_free(&ctx->c); } -int git_hash_init(git_hash_ctx *ctx) +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) { assert(ctx); mbedtls_sha1_init(&ctx->c); @@ -23,14 +31,14 @@ int git_hash_init(git_hash_ctx *ctx) return 0; } -int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) { assert(ctx); mbedtls_sha1_update(&ctx->c, data, len); return 0; } -int git_hash_final(git_oid *out, git_hash_ctx *ctx) +int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *ctx) { assert(ctx); mbedtls_sha1_finish(&ctx->c, out->id); diff --git a/src/hash/hash_mbedtls.h b/src/hash/sha1/mbedtls.h similarity index 54% rename from src/hash/hash_mbedtls.h rename to src/hash/sha1/mbedtls.h index 7f3decd7d..15f7462a4 100644 --- a/src/hash/hash_mbedtls.h +++ b/src/hash/sha1/mbedtls.h @@ -5,20 +5,15 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hash_mbedtld_h__ -#define INCLUDE_hash_mbedtld_h__ +#ifndef INCLUDE_hash_sha1_mbedtls_h__ +#define INCLUDE_hash_sha1_mbedtls_h__ + +#include "hash/sha1.h" #include -struct git_hash_ctx { +struct git_hash_sha1_ctx { mbedtls_sha1_context c; }; -#define git_hash_ctx_init(ctx) git_hash_init(ctx) - -GIT_INLINE(int) git_hash_global_init(void) -{ - return 0; -} - -#endif /* INCLUDE_hash_mbedtld_h__ */ +#endif /* INCLUDE_hash_sha1_mbedtls_h__ */ diff --git a/src/hash/hash_openssl.h b/src/hash/sha1/openssl.c similarity index 59% rename from src/hash/hash_openssl.h rename to src/hash/sha1/openssl.c index 8dbfd5ad5..ba3212ff2 100644 --- a/src/hash/hash_openssl.h +++ b/src/hash/sha1/openssl.c @@ -5,26 +5,24 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hash_hash_openssl_h__ -#define INCLUDE_hash_hash_openssl_h__ +#include "openssl.h" -#include "hash.h" - -#include - -struct git_hash_ctx { - SHA_CTX c; -}; - -#define git_hash_ctx_init(ctx) git_hash_init(ctx) -#define git_hash_ctx_cleanup(ctx) - -GIT_INLINE(int) git_hash_global_init(void) +int git_hash_sha1_global_init(void) { return 0; } -GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx) +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) +{ + return git_hash_sha1_init(ctx); +} + +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) +{ + GIT_UNUSED(ctx); +} + +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) { assert(ctx); @@ -36,7 +34,7 @@ GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx) return 0; } -GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) { assert(ctx); @@ -48,7 +46,7 @@ GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) return 0; } -GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx) +int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *ctx) { assert(ctx); @@ -59,5 +57,3 @@ GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx) return 0; } - -#endif diff --git a/src/hash/sha1/openssl.h b/src/hash/sha1/openssl.h new file mode 100644 index 000000000..a223ca03e --- /dev/null +++ b/src/hash/sha1/openssl.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_sha1_openssl_h__ +#define INCLUDE_hash_sha1_openssl_h__ + +#include "hash/sha1.h" + +#include + +struct git_hash_sha1_ctx { + SHA_CTX c; +}; + +#endif diff --git a/src/hash/sha1dc/sha1.c b/src/hash/sha1/sha1dc/sha1.c similarity index 99% rename from src/hash/sha1dc/sha1.c rename to src/hash/sha1/sha1dc/sha1.c index 25eded139..9d3cf81d4 100644 --- a/src/hash/sha1dc/sha1.c +++ b/src/hash/sha1/sha1dc/sha1.c @@ -93,13 +93,23 @@ #define SHA1DC_BIGENDIAN /* Not under GCC-alike or glibc or *BSD or newlib or */ +#elif (defined(_AIX) || defined(__hpux)) + +/* + * Defines Big Endian on a whitelist of OSs that are known to be Big + * Endian-only. See + * https://public-inbox.org/git/93056823-2740-d072-1ebd-46b440b33d7e@felt.demon.nl/ + */ +#define SHA1DC_BIGENDIAN + +/* Not under GCC-alike or glibc or *BSD or newlib or or */ #elif defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) /* * As a last resort before we do anything else we're not 100% sure * about below, we blacklist specific processors here. We could add * more, see e.g. https://wiki.debian.org/ArchitectureSpecificsMemo */ -#else /* Not under GCC-alike or glibc or *BSD or newlib or or */ +#else /* Not under GCC-alike or glibc or *BSD or newlib or or or */ /* We do nothing more here for now */ /*#error "Uncomment this to see if you fall through all the detection"*/ @@ -114,10 +124,11 @@ #endif /*ENDIANNESS SELECTION*/ +#ifndef SHA1DC_FORCE_ALIGNED_ACCESS #if defined(SHA1DC_FORCE_UNALIGNED_ACCESS) || defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR) #define SHA1DC_ALLOW_UNALIGNED_ACCESS -#endif /*UNALIGNMENT DETECTION*/ - +#endif /*UNALIGNED ACCESS DETECTION*/ +#endif /*FORCE ALIGNED ACCESS*/ #define rotate_right(x,n) (((x)>>(n))|((x)<<(32-(n)))) #define rotate_left(x,n) (((x)<<(n))|((x)>>(32-(n)))) diff --git a/src/hash/sha1dc/sha1.h b/src/hash/sha1/sha1dc/sha1.h similarity index 100% rename from src/hash/sha1dc/sha1.h rename to src/hash/sha1/sha1dc/sha1.h diff --git a/src/hash/sha1dc/ubc_check.c b/src/hash/sha1/sha1dc/ubc_check.c similarity index 100% rename from src/hash/sha1dc/ubc_check.c rename to src/hash/sha1/sha1dc/ubc_check.c diff --git a/src/hash/sha1dc/ubc_check.h b/src/hash/sha1/sha1dc/ubc_check.h similarity index 100% rename from src/hash/sha1dc/ubc_check.h rename to src/hash/sha1/sha1dc/ubc_check.h diff --git a/src/hash/hash_win32.c b/src/hash/sha1/win32.c similarity index 82% rename from src/hash/hash_win32.c rename to src/hash/sha1/win32.c index 4b6830358..c73695665 100644 --- a/src/hash/hash_win32.c +++ b/src/hash/sha1/win32.c @@ -5,15 +5,25 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "hash_win32.h" +#include "win32.h" #include "global.h" -#include "hash.h" #include #include -static struct git_hash_prov hash_prov = {0}; +#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll" + +/* BCRYPT_SHA1_ALGORITHM */ +#define GIT_HASH_CNG_HASH_TYPE L"SHA1" + +/* BCRYPT_OBJECT_LENGTH */ +#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength" + +/* BCRYPT_HASH_REUSEABLE_FLAGS */ +#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020 + +static git_hash_prov hash_prov = {0}; /* Hash initialization */ @@ -101,7 +111,7 @@ GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void) hash_prov.type = INVALID; } -static void git_hash_global_shutdown(void) +static void sha1_shutdown(void) { if (hash_prov.type == CNG) hash_cng_prov_shutdown(); @@ -109,7 +119,7 @@ static void git_hash_global_shutdown(void) hash_cryptoapi_prov_shutdown(); } -int git_hash_global_init(void) +int git_hash_sha1_global_init(void) { int error = 0; @@ -119,22 +129,22 @@ int git_hash_global_init(void) if ((error = hash_cng_prov_init()) < 0) error = hash_cryptoapi_prov_init(); - git__on_shutdown(git_hash_global_shutdown); + git__on_shutdown(sha1_shutdown); return error; } /* CryptoAPI: available in Windows XP and newer */ -GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_ctx *ctx) +GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_sha1_ctx *ctx) { ctx->type = CRYPTOAPI; ctx->prov = &hash_prov; - return git_hash_init(ctx); + return git_hash_sha1_init(ctx); } -GIT_INLINE(int) hash_cryptoapi_init(git_hash_ctx *ctx) +GIT_INLINE(int) hash_cryptoapi_init(git_hash_sha1_ctx *ctx) { if (ctx->ctx.cryptoapi.valid) CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); @@ -149,7 +159,7 @@ GIT_INLINE(int) hash_cryptoapi_init(git_hash_ctx *ctx) return 0; } -GIT_INLINE(int) hash_cryptoapi_update(git_hash_ctx *ctx, const void *_data, size_t len) +GIT_INLINE(int) hash_cryptoapi_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) { const BYTE *data = (BYTE *)_data; @@ -170,7 +180,7 @@ GIT_INLINE(int) hash_cryptoapi_update(git_hash_ctx *ctx, const void *_data, size return 0; } -GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_ctx *ctx) +GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_sha1_ctx *ctx) { DWORD len = 20; int error = 0; @@ -188,7 +198,7 @@ GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_ctx *ctx) return error; } -GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_ctx *ctx) +GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_sha1_ctx *ctx) { if (ctx->ctx.cryptoapi.valid) CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); @@ -196,7 +206,7 @@ GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_ctx *ctx) /* CNG: Available in Windows Server 2008 and newer */ -GIT_INLINE(int) hash_ctx_cng_init(git_hash_ctx *ctx) +GIT_INLINE(int) hash_ctx_cng_init(git_hash_sha1_ctx *ctx) { if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL) return -1; @@ -214,7 +224,7 @@ GIT_INLINE(int) hash_ctx_cng_init(git_hash_ctx *ctx) return 0; } -GIT_INLINE(int) hash_cng_init(git_hash_ctx *ctx) +GIT_INLINE(int) hash_cng_init(git_hash_sha1_ctx *ctx) { BYTE hash[GIT_OID_RAWSZ]; @@ -232,7 +242,7 @@ GIT_INLINE(int) hash_cng_init(git_hash_ctx *ctx) return 0; } -GIT_INLINE(int) hash_cng_update(git_hash_ctx *ctx, const void *_data, size_t len) +GIT_INLINE(int) hash_cng_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) { PBYTE data = (PBYTE)_data; @@ -251,7 +261,7 @@ GIT_INLINE(int) hash_cng_update(git_hash_ctx *ctx, const void *_data, size_t len return 0; } -GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_ctx *ctx) +GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_sha1_ctx *ctx) { if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out->id, GIT_OID_RAWSZ, 0) < 0) { git_error_set(GIT_ERROR_OS, "hash could not be finished"); @@ -263,7 +273,7 @@ GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_ctx *ctx) return 0; } -GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_ctx *ctx) +GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_sha1_ctx *ctx) { ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle); git__free(ctx->ctx.cng.hash_object); @@ -271,7 +281,7 @@ GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_ctx *ctx) /* Indirection between CryptoAPI and CNG */ -int git_hash_ctx_init(git_hash_ctx *ctx) +int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) { int error = 0; @@ -282,33 +292,33 @@ int git_hash_ctx_init(git_hash_ctx *ctx) * initialized with git_libgit2_init. Otherwise, it must be initialized * at first use. */ - if (hash_prov.type == INVALID && (error = git_hash_global_init()) < 0) + if (hash_prov.type == INVALID && (error = git_hash_sha1_global_init()) < 0) return error; - memset(ctx, 0x0, sizeof(git_hash_ctx)); + memset(ctx, 0x0, sizeof(git_hash_sha1_ctx)); return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx); } -int git_hash_init(git_hash_ctx *ctx) +int git_hash_sha1_init(git_hash_sha1_ctx *ctx) { assert(ctx && ctx->type); return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx); } -int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) { assert(ctx && ctx->type); return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len); } -int git_hash_final(git_oid *out, git_hash_ctx *ctx) +int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *ctx) { assert(ctx && ctx->type); return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx); } -void git_hash_ctx_cleanup(git_hash_ctx *ctx) +void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) { assert(ctx); diff --git a/src/hash/hash_win32.h b/src/hash/sha1/win32.h similarity index 86% rename from src/hash/hash_win32.h rename to src/hash/sha1/win32.h index 9704204e2..791d20a42 100644 --- a/src/hash/hash_win32.h +++ b/src/hash/sha1/win32.h @@ -5,12 +5,10 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#ifndef INCLUDE_hash_hash_win32_h__ -#define INCLUDE_hash_hash_win32_h__ +#ifndef INCLUDE_hash_sha1_win32_h__ +#define INCLUDE_hash_sha1_win32_h__ -#include "common.h" - -#include "hash.h" +#include "hash/sha1.h" #include #include @@ -36,17 +34,6 @@ struct hash_cryptoapi_prov { * would not exist when building in pre-Windows 2008 environments. */ -#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll" - -/* BCRYPT_SHA1_ALGORITHM */ -#define GIT_HASH_CNG_HASH_TYPE L"SHA1" - -/* BCRYPT_OBJECT_LENGTH */ -#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength" - -/* BCRYPT_HASH_REUSEABLE_FLAGS */ -#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020 - /* Function declarations for CNG */ typedef NTSTATUS (WINAPI *hash_win32_cng_open_algorithm_provider_fn)( HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm, @@ -106,14 +93,14 @@ struct hash_cng_prov { DWORD hash_object_size; }; -struct git_hash_prov { +typedef struct { enum hash_win32_prov_type type; union { struct hash_cryptoapi_prov cryptoapi; struct hash_cng_prov cng; } prov; -}; +} git_hash_prov; /* Hash contexts */ @@ -128,7 +115,7 @@ struct hash_cng_ctx { PBYTE hash_object; }; -struct git_hash_ctx { +struct git_hash_sha1_ctx { enum hash_win32_prov_type type; git_hash_prov *prov; diff --git a/src/hashsig.c b/src/hashsig.c index abebd7a54..14ec34b2f 100644 --- a/src/hashsig.c +++ b/src/hashsig.c @@ -8,7 +8,7 @@ #include "common.h" #include "git2/sys/hashsig.h" -#include "fileops.h" +#include "futils.h" #include "util.h" typedef uint32_t hashsig_t; diff --git a/src/idxmap.c b/src/idxmap.c index 3a5fc105a..4dcf963b3 100644 --- a/src/idxmap.c +++ b/src/idxmap.c @@ -32,26 +32,112 @@ static kh_inline khint_t idxentry_hash(const git_index_entry *e) __KHASH_IMPL(idx, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_equal) __KHASH_IMPL(idxicase, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_icase_equal) -int git_idxmap_alloc(git_idxmap **map) +int git_idxmap_new(git_idxmap **out) { - if ((*map = kh_init(idx)) == NULL) { - git_error_set_oom(); - return -1; - } + *out = kh_init(idx); + GIT_ERROR_CHECK_ALLOC(*out); return 0; } -int git_idxmap_icase_alloc(git_idxmap_icase **map) +int git_idxmap_icase_new(git_idxmap_icase **out) { - if ((*map = kh_init(idxicase)) == NULL) { + *out = kh_init(idxicase); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; +} + +void git_idxmap_free(git_idxmap *map) +{ + kh_destroy(idx, map); +} + +void git_idxmap_icase_free(git_idxmap_icase *map) +{ + kh_destroy(idxicase, map); +} + +void git_idxmap_clear(git_idxmap *map) +{ + kh_clear(idx, map); +} + +void git_idxmap_icase_clear(git_idxmap_icase *map) +{ + kh_clear(idxicase, map); +} + +int git_idxmap_resize(git_idxmap *map, size_t size) +{ + if (!git__is_uint32(size) || + kh_resize(idx, map, (khiter_t)size) < 0) { git_error_set_oom(); return -1; } + return 0; +} + +int git_idxmap_icase_resize(git_idxmap_icase *map, size_t size) +{ + if (!git__is_uint32(size) || + kh_resize(idxicase, map, (khiter_t)size) < 0) { + git_error_set_oom(); + return -1; + } + return 0; +} + +void *git_idxmap_get(git_idxmap *map, const git_index_entry *key) +{ + size_t idx = kh_get(idx, map, key); + if (idx == kh_end(map) || !kh_exist(map, idx)) + return NULL; + return kh_val(map, idx); +} + +int git_idxmap_set(git_idxmap *map, const git_index_entry *key, void *value) +{ + size_t idx; + int rval; + + idx = kh_put(idx, map, key, &rval); + if (rval < 0) + return -1; + + if (rval == 0) + kh_key(map, idx) = key; + + kh_val(map, idx) = value; return 0; } +int git_idxmap_icase_set(git_idxmap_icase *map, const git_index_entry *key, void *value) +{ + size_t idx; + int rval; + + idx = kh_put(idxicase, map, key, &rval); + if (rval < 0) + return -1; + + if (rval == 0) + kh_key(map, idx) = key; + + kh_val(map, idx) = value; + + return 0; +} + +void *git_idxmap_icase_get(git_idxmap_icase *map, const git_index_entry *key) +{ + size_t idx = kh_get(idxicase, map, key); + if (idx == kh_end(map) || !kh_exist(map, idx)) + return NULL; + return kh_val(map, idx); +} + void git_idxmap_insert(git_idxmap *map, const git_index_entry *key, void *value, int *rval) { khiter_t idx = kh_put(idx, map, key, rval); @@ -74,80 +160,20 @@ void git_idxmap_icase_insert(git_idxmap_icase *map, const git_index_entry *key, } } -size_t git_idxmap_lookup_index(git_idxmap *map, const git_index_entry *key) -{ - return kh_get(idx, map, key); -} - -size_t git_idxmap_icase_lookup_index(git_idxmap_icase *map, const git_index_entry *key) -{ - return kh_get(idxicase, map, key); -} - -void *git_idxmap_value_at(git_idxmap *map, size_t idx) -{ - return kh_val(map, idx); -} - -int git_idxmap_valid_index(git_idxmap *map, size_t idx) -{ - return idx != kh_end(map); -} - -int git_idxmap_has_data(git_idxmap *map, size_t idx) -{ - return kh_exist(map, idx); -} - -void git_idxmap_resize(git_idxmap *map, size_t size) -{ - kh_resize(idx, map, size); -} - -void git_idxmap_icase_resize(git_idxmap_icase *map, size_t size) -{ - kh_resize(idxicase, map, size); -} - -void git_idxmap_free(git_idxmap *map) -{ - kh_destroy(idx, map); -} - -void git_idxmap_icase_free(git_idxmap_icase *map) -{ - kh_destroy(idxicase, map); -} - -void git_idxmap_clear(git_idxmap *map) -{ - kh_clear(idx, map); -} - -void git_idxmap_icase_clear(git_idxmap_icase *map) -{ - kh_clear(idxicase, map); -} - -void git_idxmap_delete_at(git_idxmap *map, size_t idx) +int git_idxmap_delete(git_idxmap *map, const git_index_entry *key) { + khiter_t idx = kh_get(idx, map, key); + if (idx == kh_end(map)) + return GIT_ENOTFOUND; kh_del(idx, map, idx); + return 0; } -void git_idxmap_icase_delete_at(git_idxmap_icase *map, size_t idx) +int git_idxmap_icase_delete(git_idxmap_icase *map, const git_index_entry *key) { + khiter_t idx = kh_get(idxicase, map, key); + if (idx == kh_end(map)) + return GIT_ENOTFOUND; kh_del(idxicase, map, idx); -} - -void git_idxmap_delete(git_idxmap *map, const git_index_entry *key) -{ - khiter_t idx = git_idxmap_lookup_index(map, key); - if (git_idxmap_valid_index(map, idx)) - git_idxmap_delete_at(map, idx); -} -void git_idxmap_icase_delete(git_idxmap_icase *map, const git_index_entry *key) -{ - khiter_t idx = git_idxmap_icase_lookup_index(map, key); - if (git_idxmap_valid_index((git_idxmap *)map, idx)) - git_idxmap_icase_delete_at(map, idx); + return 0; } diff --git a/src/idxmap.h b/src/idxmap.h index 215a24521..76170ef32 100644 --- a/src/idxmap.h +++ b/src/idxmap.h @@ -11,31 +11,167 @@ #include "git2/index.h" +/** A map with `git_index_entry`s as key. */ typedef struct kh_idx_s git_idxmap; +/** A map with case-insensitive `git_index_entry`s as key */ typedef struct kh_idxicase_s git_idxmap_icase; -int git_idxmap_alloc(git_idxmap **map); -int git_idxmap_icase_alloc(git_idxmap_icase **map); -void git_idxmap_insert(git_idxmap *map, const git_index_entry *key, void *value, int *rval); -void git_idxmap_icase_insert(git_idxmap_icase *map, const git_index_entry *key, void *value, int *rval); +/** + * Allocate a new index entry map. + * + * @param out Pointer to the map that shall be allocated. + * @return 0 on success, an error code if allocation has failed. + */ +int git_idxmap_new(git_idxmap **out); -size_t git_idxmap_lookup_index(git_idxmap *map, const git_index_entry *key); -size_t git_idxmap_icase_lookup_index(git_idxmap_icase *map, const git_index_entry *key); -void *git_idxmap_value_at(git_idxmap *map, size_t idx); -int git_idxmap_valid_index(git_idxmap *map, size_t idx); -int git_idxmap_has_data(git_idxmap *map, size_t idx); +/** + * Allocate a new case-insensitive index entry map. + * + * @param out Pointer to the map that shall be allocated. + * @return 0 on success, an error code if allocation has failed. + */ +int git_idxmap_icase_new(git_idxmap_icase **out); -void git_idxmap_resize(git_idxmap *map, size_t size); -void git_idxmap_icase_resize(git_idxmap_icase *map, size_t size); +/** + * Free memory associated with the map. + * + * Note that this function will _not_ free values added to this + * map. + * + * @param map Pointer to the map that is to be free'd. May be + * `NULL`. + */ void git_idxmap_free(git_idxmap *map); + +/** + * Free memory associated with the map. + * + * Note that this function will _not_ free values added to this + * map. + * + * @param map Pointer to the map that is to be free'd. May be + * `NULL`. + */ void git_idxmap_icase_free(git_idxmap_icase *map); + +/** + * Clear all entries from the map. + * + * This function will remove all entries from the associated map. + * Memory associated with it will not be released, though. + * + * @param map Pointer to the map that shall be cleared. May be + * `NULL`. + */ void git_idxmap_clear(git_idxmap *map); + +/** + * Clear all entries from the map. + * + * This function will remove all entries from the associated map. + * Memory associated with it will not be released, though. + * + * @param map Pointer to the map that shall be cleared. May be + * `NULL`. + */ void git_idxmap_icase_clear(git_idxmap_icase *map); -void git_idxmap_delete_at(git_idxmap *map, size_t idx); -void git_idxmap_icase_delete_at(git_idxmap_icase *map, size_t idx); +/** + * Resize the map by allocating more memory. + * + * @param map map that shall be resized + * @param size count of entries that the map shall hold + * @return `0` if the map was successfully resized, a negative + * error code otherwise + */ +int git_idxmap_resize(git_idxmap *map, size_t size); -void git_idxmap_delete(git_idxmap *map, const git_index_entry *key); -void git_idxmap_icase_delete(git_idxmap_icase *map, const git_index_entry *key); +/** + * Resize the map by allocating more memory. + * + * @param map map that shall be resized + * @param size count of entries that the map shall hold + * @return `0` if the map was successfully resized, a negative + * error code otherwise + */ +int git_idxmap_icase_resize(git_idxmap_icase *map, size_t size); + +/** + * Return value associated with the given key. + * + * @param map map to search key in + * @param key key to search for; the index entry will be searched + * for by its case-sensitive path + * @return value associated with the given key or NULL if the key was not found + */ +void *git_idxmap_get(git_idxmap *map, const git_index_entry *key); + +/** + * Return value associated with the given key. + * + * @param map map to search key in + * @param key key to search for; the index entry will be searched + * for by its case-insensitive path + * @return value associated with the given key or NULL if the key was not found + */ +void *git_idxmap_icase_get(git_idxmap_icase *map, const git_index_entry *key); + +/** + * Set the entry for key to value. + * + * If the map has no corresponding entry for the given key, a new + * entry will be created with the given value. If an entry exists + * already, its value will be updated to match the given value. + * + * @param map map to create new entry in + * @param key key to set + * @param value value to associate the key with; may be NULL + * @return zero if the key was successfully set, a negative error + * code otherwise + */ +int git_idxmap_set(git_idxmap *map, const git_index_entry *key, void *value); + +/** + * Set the entry for key to value. + * + * If the map has no corresponding entry for the given key, a new + * entry will be created with the given value. If an entry exists + * already, its value will be updated to match the given value. + * + * @param map map to create new entry in + * @param key key to set + * @param value value to associate the key with; may be NULL + * @return zero if the key was successfully set, a negative error + * code otherwise + */ +int git_idxmap_icase_set(git_idxmap_icase *map, const git_index_entry *key, void *value); + +/** + * Delete an entry from the map. + * + * Delete the given key and its value from the map. If no such + * key exists, this will do nothing. + * + * @param map map to delete key in + * @param key key to delete + * @return `0` if the key has been deleted, GIT_ENOTFOUND if no + * such key was found, a negative code in case of an + * error + */ +int git_idxmap_delete(git_idxmap *map, const git_index_entry *key); + +/** + * Delete an entry from the map. + * + * Delete the given key and its value from the map. If no such + * key exists, this will do nothing. + * + * @param map map to delete key in + * @param key key to delete + * @return `0` if the key has been deleted, GIT_ENOTFOUND if no + * such key was found, a negative code in case of an + * error + */ +int git_idxmap_icase_delete(git_idxmap_icase *map, const git_index_entry *key); #endif diff --git a/src/ignore.c b/src/ignore.c index 5427efa29..f5fb507e6 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -12,7 +12,7 @@ #include "attrcache.h" #include "path.h" #include "config.h" -#include "fnmatch.h" +#include "wildmatch.h" #define GIT_IGNORE_INTERNAL "[internal]exclude" @@ -101,7 +101,7 @@ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) */ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match) { - int error = 0, fnflags; + int error = 0, wildmatch_flags; size_t i; git_attr_fnmatch *rule; char *path; @@ -109,9 +109,9 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match *out = 0; - fnflags = FNM_PATHNAME; + wildmatch_flags = WM_PATHNAME; if (match->flags & GIT_ATTR_FNMATCH_ICASE) - fnflags |= FNM_IGNORECASE; + wildmatch_flags |= WM_CASEFOLD; /* path of the file relative to the workdir, so we match the rules in subdirs */ if (match->containing_dir) { @@ -141,13 +141,8 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match if (git_buf_oom(&buf)) goto out; - if ((error = p_fnmatch(git_buf_cstr(&buf), path, fnflags)) < 0) { - git_error_set(GIT_ERROR_INVALID, "error matching pattern"); - goto out; - } - /* if we found a match, we want to keep this rule */ - if (error != FNM_NOMATCH) { + if ((wildmatch(git_buf_cstr(&buf), path, wildmatch_flags)) == WM_MATCH) { *out = 1; error = 0; goto out; @@ -163,14 +158,16 @@ out: } static int parse_ignore_file( - git_repository *repo, git_attr_file *attrs, const char *data) + git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros) { int error = 0; int ignore_case = false; const char *scan = data, *context = NULL; git_attr_fnmatch *match = NULL; - if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0) + GIT_UNUSED(allow_macros); + + if (git_repository__configmap_lookup(&ignore_case, repo, GIT_CONFIGMAP_IGNORECASE) < 0) git_error_clear(); /* if subdir file path, convert context for file paths */ @@ -193,9 +190,7 @@ static int parse_ignore_file( } match->flags = - GIT_ATTR_FNMATCH_ALLOWSPACE | - GIT_ATTR_FNMATCH_ALLOWNEG | - GIT_ATTR_FNMATCH_NOLEADINGDIR; + GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; if (!(error = git_attr_fnmatch__parse( match, &attrs->pool, context, &scan))) @@ -246,9 +241,8 @@ static int push_ignore_file( int error = 0; git_attr_file *file = NULL; - error = git_attr_cache__get( - &file, ignores->repo, NULL, GIT_ATTR_FILE__FROM_FILE, - base, filename, parse_ignore_file); + error = git_attr_cache__get(&file, ignores->repo, NULL, GIT_ATTR_FILE__FROM_FILE, + base, filename, parse_ignore_file, false); if (error < 0) return error; @@ -274,12 +268,12 @@ static int get_internal_ignores(git_attr_file **out, git_repository *repo) if ((error = git_attr_cache__init(repo)) < 0) return error; - error = git_attr_cache__get( - out, repo, NULL, GIT_ATTR_FILE__IN_MEMORY, NULL, GIT_IGNORE_INTERNAL, NULL); + error = git_attr_cache__get(out, repo, NULL, GIT_ATTR_FILE__IN_MEMORY, NULL, + GIT_IGNORE_INTERNAL, NULL, false); /* if internal rules list is empty, insert default rules */ if (!error && !(*out)->rules.length) - error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES); + error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES, false); return error; } @@ -299,8 +293,8 @@ int git_ignore__for_path( ignores->repo = repo; /* Read the ignore_case flag */ - if ((error = git_repository__cvar( - &ignores->ignore_case, repo, GIT_CVAR_IGNORECASE)) < 0) + if ((error = git_repository__configmap_lookup( + &ignores->ignore_case, repo, GIT_CONFIGMAP_IGNORECASE)) < 0) goto cleanup; if ((error = git_attr_cache__init(repo)) < 0) @@ -337,16 +331,13 @@ int git_ignore__for_path( goto cleanup; } - if ((error = git_repository_item_path(&infopath, - repo, GIT_REPOSITORY_ITEM_INFO)) < 0) - goto cleanup; - - /* load .git/info/exclude */ - error = push_ignore_file( - ignores, &ignores->ign_global, - infopath.ptr, GIT_IGNORE_FILE_INREPO); - if (error < 0) - goto cleanup; + /* load .git/info/exclude if possible */ + if ((error = git_repository_item_path(&infopath, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || + (error = push_ignore_file(ignores, &ignores->ign_global, infopath.ptr, GIT_IGNORE_FILE_INREPO)) < 0) { + if (error != GIT_ENOTFOUND) + goto cleanup; + error = 0; + } /* load core.excludesfile */ if (git_repository_attr_cache(repo)->cfg_excl_file != NULL) @@ -453,7 +444,7 @@ static bool ignore_lookup_in_rules( int git_ignore__lookup( int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag) { - unsigned int i; + size_t i; git_attr_file *file; git_attr_path path; @@ -467,8 +458,11 @@ int git_ignore__lookup( if (ignore_lookup_in_rules(out, ignores->ign_internal, &path)) goto cleanup; - /* next process files in the path */ - git_vector_foreach(&ignores->ign_path, i, file) { + /* next process files in the path. + * this process has to process ignores in reverse order + * to ensure correct prioritization of rules + */ + git_vector_rforeach(&ignores->ign_path, i, file) { if (ignore_lookup_in_rules(out, file, &path)) goto cleanup; } @@ -492,7 +486,7 @@ int git_ignore_add_rule(git_repository *repo, const char *rules) if ((error = get_internal_ignores(&ign_internal, repo)) < 0) return error; - error = parse_ignore_file(repo, ign_internal, rules); + error = parse_ignore_file(repo, ign_internal, rules, false); git_attr_file__free(ign_internal); return error; @@ -508,7 +502,7 @@ int git_ignore_clear_internal_rules(git_repository *repo) if (!(error = git_attr_file__clear_rules(ign_internal, true))) error = parse_ignore_file( - repo, ign_internal, GIT_IGNORE_DEFAULT_RULES); + repo, ign_internal, GIT_IGNORE_DEFAULT_RULES, false); git_attr_file__free(ign_internal); return error; diff --git a/src/index.c b/src/index.c index 7f865c2c7..36a8bdb7b 100644 --- a/src/index.c +++ b/src/index.c @@ -27,29 +27,6 @@ #include "git2/config.h" #include "git2/sys/index.h" -#define INSERT_IN_MAP_EX(idx, map, e, err) do { \ - if ((idx)->ignore_case) \ - git_idxmap_icase_insert((git_idxmap_icase *) (map), (e), (e), (err)); \ - else \ - git_idxmap_insert((map), (e), (e), (err)); \ - } while (0) - -#define INSERT_IN_MAP(idx, e, err) INSERT_IN_MAP_EX(idx, (idx)->entries_map, e, err) - -#define LOOKUP_IN_MAP(p, idx, k) do { \ - if ((idx)->ignore_case) \ - (p) = git_idxmap_icase_lookup_index((git_idxmap_icase *) index->entries_map, (k)); \ - else \ - (p) = git_idxmap_lookup_index(index->entries_map, (k)); \ - } while (0) - -#define DELETE_IN_MAP(idx, e) do { \ - if ((idx)->ignore_case) \ - git_idxmap_icase_delete((git_idxmap_icase *) (idx)->entries_map, (e)); \ - else \ - git_idxmap_delete((idx)->entries_map, (e)); \ - } while (0) - static int index_apply_to_wd_diff(git_index *index, int action, const git_strarray *paths, unsigned int flags, git_index_matched_path_cb cb, void *payload); @@ -148,6 +125,30 @@ static int write_index(git_oid *checksum, git_index *index, git_filebuf *file); static void index_entry_free(git_index_entry *entry); static void index_entry_reuc_free(git_index_reuc_entry *reuc); +GIT_INLINE(int) index_map_set(git_idxmap *map, git_index_entry *e, bool ignore_case) +{ + if (ignore_case) + return git_idxmap_icase_set((git_idxmap_icase *) map, e, e); + else + return git_idxmap_set(map, e, e); +} + +GIT_INLINE(int) index_map_delete(git_idxmap *map, git_index_entry *e, bool ignore_case) +{ + if (ignore_case) + return git_idxmap_icase_delete((git_idxmap_icase *) map, e); + else + return git_idxmap_delete(map, e); +} + +GIT_INLINE(int) index_map_resize(git_idxmap *map, size_t count, bool ignore_case) +{ + if (ignore_case) + return git_idxmap_icase_resize((git_idxmap_icase *) map, count); + else + return git_idxmap_resize(map, count); +} + int git_index_entry_srch(const void *key, const void *array_member) { const struct entry_srch_key *srch_key = key; @@ -423,10 +424,10 @@ int git_index_open(git_index **index_out, const char *index_path) } if (git_vector_init(&index->entries, 32, git_index_entry_cmp) < 0 || - git_idxmap_alloc(&index->entries_map) < 0 || - git_vector_init(&index->names, 8, conflict_name_cmp) < 0 || - git_vector_init(&index->reuc, 8, reuc_cmp) < 0 || - git_vector_init(&index->deleted, 8, git_index_entry_cmp) < 0) + git_idxmap_new(&index->entries_map) < 0 || + git_vector_init(&index->names, 8, conflict_name_cmp) < 0 || + git_vector_init(&index->reuc, 8, reuc_cmp) < 0 || + git_vector_init(&index->deleted, 8, git_index_entry_cmp) < 0) goto fail; index->entries_cmp_path = git__strcmp_cb; @@ -507,7 +508,7 @@ static int index_remove_entry(git_index *index, size_t pos) if (entry != NULL) { git_tree_cache_invalidate_path(index->tree, entry->path); - DELETE_IN_MAP(index, entry); + index_map_delete(index->entries_map, entry, index->ignore_case); } error = git_vector_remove(&index->entries, pos); @@ -538,13 +539,19 @@ int git_index_clear(git_index *index) git_idxmap_clear(index->entries_map); while (!error && index->entries.length > 0) error = index_remove_entry(index, index->entries.length - 1); + + if (error) + goto done; + index_free_deleted(index); - git_index_reuc_clear(index); - git_index_name_clear(index); + if ((error = git_index_name_clear(index)) < 0 || + (error = git_index_reuc_clear(index)) < 0) + goto done; git_futils_filestamp_set(&index->stamp, NULL); +done: return error; } @@ -570,11 +577,11 @@ int git_index_set_caps(git_index *index, int caps) return create_index_error( -1, "cannot access repository to set index caps"); - if (!git_repository__cvar(&val, repo, GIT_CVAR_IGNORECASE)) + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_IGNORECASE)) index->ignore_case = (val != 0); - if (!git_repository__cvar(&val, repo, GIT_CVAR_FILEMODE)) + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_FILEMODE)) index->distrust_filemode = (val == 0); - if (!git_repository__cvar(&val, repo, GIT_CVAR_SYMLINKS)) + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_SYMLINKS)) index->no_symlinks = (val == 0); } else { @@ -852,20 +859,24 @@ const git_index_entry *git_index_get_bypath( git_index *index, const char *path, int stage) { git_index_entry key = {{ 0 }}; - size_t pos; + git_index_entry *value; assert(index); key.path = path; GIT_INDEX_ENTRY_STAGE_SET(&key, stage); - LOOKUP_IN_MAP(pos, index, &key); + if (index->ignore_case) + value = git_idxmap_icase_get((git_idxmap_icase *) index->entries_map, &key); + else + value = git_idxmap_get(index->entries_map, &key); - if (git_idxmap_valid_index(index->entries_map, pos)) - return git_idxmap_value_at(index->entries_map, pos); + if (!value) { + git_error_set(GIT_ERROR_INDEX, "index does not contain '%s'", path); + return NULL; + } - git_error_set(GIT_ERROR_INDEX, "index does not contain '%s'", path); - return NULL; + return value; } void git_index_entry__init_from_stat( @@ -1398,10 +1409,9 @@ static int index_insert( * at the sorted position. (Since we re-sort after each insert to * check for dups, this is actually cheaper in the long run.) */ - if ((error = git_vector_insert_sorted(&index->entries, entry, index_no_dups)) < 0) + if ((error = git_vector_insert_sorted(&index->entries, entry, index_no_dups)) < 0 || + (error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) goto out; - - INSERT_IN_MAP(index, entry, &error); } index->dirty = 1; @@ -1453,7 +1463,7 @@ GIT_INLINE(bool) valid_filemode(const int filemode) return (is_file_or_link(filemode) || filemode == GIT_FILEMODE_COMMIT); } -int git_index_add_frombuffer( +int git_index_add_from_buffer( git_index *index, const git_index_entry *source_entry, const void *buffer, size_t len) { @@ -1473,17 +1483,22 @@ int git_index_add_frombuffer( return -1; } + if (len > UINT32_MAX) { + git_error_set(GIT_ERROR_INDEX, "buffer is too large"); + return -1; + } + if (index_entry_dup(&entry, index, source_entry) < 0) return -1; - error = git_blob_create_frombuffer(&id, INDEX_OWNER(index), buffer, len); + error = git_blob_create_from_buffer(&id, INDEX_OWNER(index), buffer, len); if (error < 0) { index_entry_free(entry); return error; } git_oid_cpy(&entry->id, &id); - entry->file_size = len; + entry->file_size = (uint32_t)len; if ((error = index_insert(index, &entry, 1, true, true, true)) < 0) return error; @@ -1610,41 +1625,42 @@ int git_index_remove_bypath(git_index *index, const char *path) int git_index__fill(git_index *index, const git_vector *source_entries) { const git_index_entry *source_entry = NULL; + int error = 0; size_t i; - int ret = 0; assert(index); if (!source_entries->length) return 0; - git_vector_size_hint(&index->entries, source_entries->length); - git_idxmap_resize(index->entries_map, (size_t)(source_entries->length * 1.3)); + if (git_vector_size_hint(&index->entries, source_entries->length) < 0 || + index_map_resize(index->entries_map, (size_t)(source_entries->length * 1.3), + index->ignore_case) < 0) + return -1; git_vector_foreach(source_entries, i, source_entry) { git_index_entry *entry = NULL; - if ((ret = index_entry_dup(&entry, index, source_entry)) < 0) + if ((error = index_entry_dup(&entry, index, source_entry)) < 0) break; index_entry_adjust_namemask(entry, ((struct entry_internal *)entry)->pathlen); entry->flags_extended |= GIT_INDEX_ENTRY_UPTODATE; entry->mode = git_index__create_mode(entry->mode); - if ((ret = git_vector_insert(&index->entries, entry)) < 0) + if ((error = git_vector_insert(&index->entries, entry)) < 0) break; - INSERT_IN_MAP(index, entry, &ret); - if (ret < 0) + if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) break; index->dirty = 1; } - if (!ret) + if (!error) git_vector_sort(&index->entries); - return ret; + return error; } @@ -1677,7 +1693,7 @@ int git_index_remove(git_index *index, const char *path, int stage) remove_key.path = path; GIT_INDEX_ENTRY_STAGE_SET(&remove_key, stage); - DELETE_IN_MAP(index, &remove_key); + index_map_delete(index->entries_map, &remove_key, index->ignore_case); if (index_find(&position, index, path, 0, stage) < 0) { git_error_set( @@ -2126,7 +2142,7 @@ int git_index_name_add(git_index *index, return 0; } -void git_index_name_clear(git_index *index) +int git_index_name_clear(git_index *index) { size_t i; git_index_name_entry *conflict_name; @@ -2139,6 +2155,8 @@ void git_index_name_clear(git_index *index) git_vector_clear(&index->names); index->dirty = 1; + + return 0; } size_t git_index_reuc_entrycount(git_index *index) @@ -2235,7 +2253,7 @@ int git_index_reuc_remove(git_index *index, size_t position) return error; } -void git_index_reuc_clear(git_index *index) +int git_index_reuc_clear(git_index *index) { size_t i; @@ -2247,6 +2265,8 @@ void git_index_reuc_clear(git_index *index) git_vector_clear(&index->reuc); index->dirty = 1; + + return 0; } static int index_error_invalid(const char *message) @@ -2607,10 +2627,8 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) assert(!index->entries.length); - if (index->ignore_case) - git_idxmap_icase_resize((git_idxmap_icase *) index->entries_map, header.entry_count); - else - git_idxmap_resize(index->entries_map, header.entry_count); + if ((error = index_map_resize(index->entries_map, header.entry_count, index->ignore_case)) < 0) + return error; /* Parse all the entries */ for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) { @@ -2627,9 +2645,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) goto done; } - INSERT_IN_MAP(index, entry, &error); - - if (error < 0) { + if ((error = index_map_set(index->entries_map, entry, index->ignore_case)) < 0) { index_entry_free(entry); goto done; } @@ -2728,7 +2744,7 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const cha ++same_len; } path_len -= same_len; - varint_len = git_encode_varint(NULL, 0, same_len); + varint_len = git_encode_varint(NULL, 0, strlen(last) - same_len); } disk_size = index_entry_size(path_len, varint_len, entry->flags); @@ -2779,7 +2795,7 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const cha if (last) { varint_len = git_encode_varint((unsigned char *) path, - disk_size, same_len); + disk_size, strlen(last) - same_len); assert(varint_len > 0); path += varint_len; disk_size -= varint_len; @@ -3106,7 +3122,7 @@ int git_index_read_tree(git_index *index, const git_tree *tree) size_t i; git_index_entry *e; - if (git_idxmap_alloc(&entries_map) < 0) + if (git_idxmap_new(&entries_map) < 0) return -1; git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */ @@ -3124,15 +3140,11 @@ int git_index_read_tree(git_index *index, const git_tree *tree) if ((error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data)) < 0) goto cleanup; - if (index->ignore_case) - git_idxmap_icase_resize((git_idxmap_icase *) entries_map, entries.length); - else - git_idxmap_resize(entries_map, entries.length); + if ((error = index_map_resize(entries_map, entries.length, index->ignore_case)) < 0) + goto cleanup; git_vector_foreach(&entries, i, e) { - INSERT_IN_MAP_EX(index, entries_map, e, &error); - - if (error < 0) { + if ((error = index_map_set(entries_map, e, index->ignore_case)) < 0) { git_error_set(GIT_ERROR_INDEX, "failed to insert entry into map"); return error; } @@ -3180,14 +3192,13 @@ static int git_index_read_iterator( assert((new_iterator->flags & GIT_ITERATOR_DONT_IGNORE_CASE)); if ((error = git_vector_init(&new_entries, new_length_hint, index->entries._cmp)) < 0 || - (error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0 || - (error = git_idxmap_alloc(&new_entries_map)) < 0) + (error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0 || + (error = git_idxmap_new(&new_entries_map)) < 0) goto done; - if (index->ignore_case && new_length_hint) - git_idxmap_icase_resize((git_idxmap_icase *) new_entries_map, new_length_hint); - else if (new_length_hint) - git_idxmap_resize(new_entries_map, new_length_hint); + if (new_length_hint && (error = index_map_resize(new_entries_map, new_length_hint, + index->ignore_case)) < 0) + goto done; opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_CONFLICTS; @@ -3251,7 +3262,8 @@ static int git_index_read_iterator( if (add_entry) { if ((error = git_vector_insert(&new_entries, add_entry)) == 0) - INSERT_IN_MAP_EX(index, new_entries_map, add_entry, &error); + error = index_map_set(new_entries_map, add_entry, + index->ignore_case); } if (remove_entry && error >= 0) @@ -3275,8 +3287,9 @@ static int git_index_read_iterator( } } - git_index_name_clear(index); - git_index_reuc_clear(index); + if ((error = git_index_name_clear(index)) < 0 || + (error = git_index_reuc_clear(index)) < 0) + goto done; git_vector_swap(&new_entries, &index->entries); new_entries_map = git__swap(index->entries_map, new_entries_map); @@ -3700,3 +3713,12 @@ void git_indexwriter_cleanup(git_indexwriter *writer) git_index_free(writer->index); writer->index = NULL; } + +/* Deprecated functions */ + +int git_index_add_frombuffer( + git_index *index, const git_index_entry *source_entry, + const void *buffer, size_t len) +{ + return git_index_add_from_buffer(index, source_entry, buffer, len); +} diff --git a/src/index.h b/src/index.h index 982afed3a..54402f563 100644 --- a/src/index.h +++ b/src/index.h @@ -9,7 +9,7 @@ #include "common.h" -#include "fileops.h" +#include "futils.h" #include "filebuf.h" #include "vector.h" #include "idxmap.h" diff --git a/src/indexer.c b/src/indexer.c index 27b763790..68fdd85c5 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -19,6 +19,7 @@ #include "pack.h" #include "filebuf.h" #include "oid.h" +#include "oidarray.h" #include "oidmap.h" #include "zstream.h" #include "object.h" @@ -46,8 +47,8 @@ struct git_indexer { struct git_pack_header hdr; struct git_pack_file *pack; unsigned int mode; - git_off_t off; - git_off_t entry_start; + off64_t off; + off64_t entry_start; git_object_t entry_type; git_buf entry_data; git_packfile_stream stream; @@ -57,7 +58,7 @@ struct git_indexer { unsigned int fanout[256]; git_hash_ctx hash_ctx; git_oid hash; - git_transfer_progress_cb progress_cb; + git_indexer_progress_cb progress_cb; void *progress_payload; char objbuf[8*1024]; @@ -74,7 +75,7 @@ struct git_indexer { }; struct delta_info { - git_off_t delta_off; + off64_t delta_off; }; const git_oid *git_indexer_hash(const git_indexer *idx) @@ -115,13 +116,18 @@ static int objects_cmp(const void *a, const void *b) return git_oid__cmp(&entrya->oid, &entryb->oid); } -int git_indexer_init_options(git_indexer_options *opts, unsigned int version) +int git_indexer_options_init(git_indexer_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_indexer_options, GIT_INDEXER_OPTIONS_INIT); return 0; } +int git_indexer_init_options(git_indexer_options *opts, unsigned int version) +{ + return git_indexer_options_init(opts, version); +} + int git_indexer_new( git_indexer **out, const char *prefix, @@ -144,11 +150,12 @@ int git_indexer_new( idx->progress_cb = opts.progress_cb; idx->progress_payload = opts.progress_cb_payload; idx->mode = mode ? mode : GIT_PACK_FILE_MODE; - git_hash_ctx_init(&idx->hash_ctx); - git_hash_ctx_init(&idx->trailer); git_buf_init(&idx->entry_data, 0); - idx->expected_oids = git_oidmap_alloc(); - GIT_ERROR_CHECK_ALLOC(idx->expected_oids); + + if ((error = git_hash_ctx_init(&idx->hash_ctx)) < 0 || + (error = git_hash_ctx_init(&idx->trailer)) < 0 || + (error = git_oidmap_new(&idx->expected_oids)) < 0) + goto cleanup; idx->do_verify = opts.verify; @@ -213,7 +220,7 @@ static int store_delta(git_indexer *idx) return 0; } -static int hash_header(git_hash_ctx *ctx, git_off_t len, git_object_t type) +static int hash_header(git_hash_ctx *ctx, off64_t len, git_object_t type) { char buffer[64]; size_t hdrlen; @@ -258,7 +265,7 @@ static int advance_delta_offset(git_indexer *idx, git_object_t type) if (type == GIT_OBJECT_REF_DELTA) { idx->off += GIT_OID_RAWSZ; } else { - git_off_t base_off = get_delta_base(idx->pack, &w, &idx->off, type, idx->entry_start); + off64_t base_off = get_delta_base(idx->pack, &w, &idx->off, type, idx->entry_start); git_mwindow_close(&w); if (base_off < 0) return (int)base_off; @@ -284,7 +291,7 @@ static int read_object_stream(git_indexer *idx, git_packfile_stream *stream) return 0; } -static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, git_off_t start, git_off_t size) +static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, off64_t start, off64_t size) { void *ptr; uint32_t crc; @@ -308,10 +315,8 @@ static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, git_off_t start, return 0; } -static void add_expected_oid(git_indexer *idx, const git_oid *oid) +static int add_expected_oid(git_indexer *idx, const git_oid *oid) { - int ret; - /* * If we know about that object because it is stored in our ODB or * because we have already processed it as part of our pack file, we do @@ -321,15 +326,18 @@ static void add_expected_oid(git_indexer *idx, const git_oid *oid) !git_oidmap_exists(idx->pack->idx_cache, oid) && !git_oidmap_exists(idx->expected_oids, oid)) { git_oid *dup = git__malloc(sizeof(*oid)); + GIT_ERROR_CHECK_ALLOC(dup); git_oid_cpy(dup, oid); - git_oidmap_put(idx->expected_oids, dup, &ret); + return git_oidmap_set(idx->expected_oids, dup, dup); } + + return 0; } static int check_object_connectivity(git_indexer *idx, const git_rawobj *obj) { git_object *object; - size_t keyidx; + git_oid *expected; int error; if (obj->type != GIT_OBJECT_BLOB && @@ -341,11 +349,9 @@ static int check_object_connectivity(git_indexer *idx, const git_rawobj *obj) if ((error = git_object__from_raw(&object, obj->data, obj->len, obj->type)) < 0) goto out; - keyidx = git_oidmap_lookup_index(idx->expected_oids, &object->cached.oid); - if (git_oidmap_valid_index(idx->expected_oids, keyidx)) { - const git_oid *key = git_oidmap_key(idx->expected_oids, keyidx); - git__free((git_oid *) key); - git_oidmap_delete_at(idx->expected_oids, keyidx); + if ((expected = git_oidmap_get(idx->expected_oids, &object->cached.oid)) != NULL) { + git_oidmap_delete(idx->expected_oids, &object->cached.oid); + git__free(expected); } /* @@ -363,7 +369,8 @@ static int check_object_connectivity(git_indexer *idx, const git_rawobj *obj) size_t i; git_array_foreach(tree->entries, i, entry) - add_expected_oid(idx, entry->oid); + if (add_expected_oid(idx, entry->oid) < 0) + goto out; break; } @@ -374,9 +381,11 @@ static int check_object_connectivity(git_indexer *idx, const git_rawobj *obj) size_t i; git_array_foreach(commit->parent_ids, i, parent_oid) - add_expected_oid(idx, parent_oid); + if (add_expected_oid(idx, parent_oid) < 0) + goto out; - add_expected_oid(idx, &commit->tree_id); + if (add_expected_oid(idx, &commit->tree_id) < 0) + goto out; break; } @@ -384,7 +393,8 @@ static int check_object_connectivity(git_indexer *idx, const git_rawobj *obj) { git_tag *tag = (git_tag *) object; - add_expected_oid(idx, &tag->target); + if (add_expected_oid(idx, &tag->target) < 0) + goto out; break; } @@ -402,12 +412,11 @@ out: static int store_object(git_indexer *idx) { int i, error; - size_t k; git_oid oid; struct entry *entry; - git_off_t entry_size; + off64_t entry_size; struct git_pack_entry *pentry; - git_off_t entry_start = idx->entry_start; + off64_t entry_start = idx->entry_start; entry = git__calloc(1, sizeof(*entry)); GIT_ERROR_CHECK_ALLOC(entry); @@ -438,21 +447,17 @@ static int store_object(git_indexer *idx) git_oid_cpy(&pentry->sha1, &oid); pentry->offset = entry_start; - k = git_oidmap_put(idx->pack->idx_cache, &pentry->sha1, &error); - if (error == -1) { - git__free(pentry); - git_error_set_oom(); - goto on_error; - } - - if (error == 0) { + if (git_oidmap_exists(idx->pack->idx_cache, &pentry->sha1)) { git_error_set(GIT_ERROR_INDEXER, "duplicate object %s found in pack", git_oid_tostr_s(&pentry->sha1)); git__free(pentry); goto on_error; } - - git_oidmap_set_value_at(idx->pack->idx_cache, k, pentry); + if ((error = git_oidmap_set(idx->pack->idx_cache, &pentry->sha1, pentry)) < 0) { + git__free(pentry); + git_error_set_oom(); + goto on_error; + } git_oid_cpy(&entry->oid, &oid); @@ -480,10 +485,9 @@ GIT_INLINE(bool) has_entry(git_indexer *idx, git_oid *id) return git_oidmap_exists(idx->pack->idx_cache, id); } -static int save_entry(git_indexer *idx, struct entry *entry, struct git_pack_entry *pentry, git_off_t entry_start) +static int save_entry(git_indexer *idx, struct entry *entry, struct git_pack_entry *pentry, off64_t entry_start) { - int i, error; - size_t k; + int i; if (entry_start > UINT31_MAX) { entry->offset = UINT32_MAX; @@ -493,15 +497,13 @@ static int save_entry(git_indexer *idx, struct entry *entry, struct git_pack_ent } pentry->offset = entry_start; - k = git_oidmap_put(idx->pack->idx_cache, &pentry->sha1, &error); - if (error <= 0) { + if (git_oidmap_exists(idx->pack->idx_cache, &pentry->sha1) || + git_oidmap_set(idx->pack->idx_cache, &pentry->sha1, pentry) < 0) { git_error_set(GIT_ERROR_INDEXER, "cannot insert object into pack"); return -1; } - git_oidmap_set_value_at(idx->pack->idx_cache, k, pentry); - /* Add the object to the list */ if (git_vector_insert(&idx->objects, entry) < 0) return -1; @@ -513,7 +515,7 @@ static int save_entry(git_indexer *idx, struct entry *entry, struct git_pack_ent return 0; } -static int hash_and_save(git_indexer *idx, git_rawobj *obj, git_off_t entry_start) +static int hash_and_save(git_indexer *idx, git_rawobj *obj, off64_t entry_start) { git_oid oid; size_t entry_size; @@ -548,7 +550,7 @@ on_error: return -1; } -static int do_progress_callback(git_indexer *idx, git_transfer_progress *stats) +static int do_progress_callback(git_indexer *idx, git_indexer_progress *stats) { if (idx->progress_cb) return git_error_set_after_callback_function( @@ -594,12 +596,12 @@ static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size) idx->inbuf_len += size - to_expell; } -static int write_at(git_indexer *idx, const void *data, git_off_t offset, size_t size) +static int write_at(git_indexer *idx, const void *data, off64_t offset, size_t size) { git_file fd = idx->pack->mwf.fd; size_t mmap_alignment; size_t page_offset; - git_off_t page_start; + off64_t page_start; unsigned char *map_data; git_map map; int error; @@ -625,11 +627,11 @@ static int write_at(git_indexer *idx, const void *data, git_off_t offset, size_t static int append_to_pack(git_indexer *idx, const void *data, size_t size) { - git_off_t new_size; + off64_t new_size; size_t mmap_alignment; size_t page_offset; - git_off_t page_start; - git_off_t current_size = idx->pack->mwf.size; + off64_t page_start; + off64_t current_size = idx->pack->mwf.size; int fd = idx->pack->mwf.fd; int error; @@ -656,10 +658,10 @@ static int append_to_pack(git_indexer *idx, const void *data, size_t size) return write_at(idx, data, idx->pack->mwf.size, size); } -static int read_stream_object(git_indexer *idx, git_transfer_progress *stats) +static int read_stream_object(git_indexer *idx, git_indexer_progress *stats) { git_packfile_stream *stream = &idx->stream; - git_off_t entry_start = idx->off; + off64_t entry_start = idx->off; size_t entry_size; git_object_t type; git_mwindow *w = NULL; @@ -745,7 +747,7 @@ static int read_stream_object(git_indexer *idx, git_transfer_progress *stats) return 0; } -int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats) +int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_indexer_progress *stats) { int error = -1; struct git_pack_header *hdr = &idx->hdr; @@ -781,8 +783,8 @@ int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_tran return -1; } - idx->pack->idx_cache = git_oidmap_alloc(); - GIT_ERROR_CHECK_ALLOC(idx->pack->idx_cache); + if (git_oidmap_new(&idx->pack->idx_cache) < 0) + return -1; idx->pack->has_cache = 1; if (git_vector_init(&idx->objects, total_objects, objects_cmp) < 0) @@ -863,7 +865,7 @@ static int inject_object(git_indexer *idx, git_oid *id) git_oid foo = {{0}}; unsigned char hdr[64]; git_buf buf = GIT_BUF_INIT; - git_off_t entry_start; + off64_t entry_start; const void *data; size_t len, hdr_len; int error; @@ -929,7 +931,7 @@ cleanup: return error; } -static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats) +static int fix_thin_pack(git_indexer *idx, git_indexer_progress *stats) { int error, found_ref_delta = 0; unsigned int i; @@ -937,7 +939,7 @@ static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats) size_t size; git_object_t type; git_mwindow *w = NULL; - git_off_t curpos = 0; + off64_t curpos = 0; unsigned char *base_info; unsigned int left = 0; git_oid base; @@ -991,7 +993,7 @@ static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats) return 0; } -static int resolve_deltas(git_indexer *idx, git_transfer_progress *stats) +static int resolve_deltas(git_indexer *idx, git_indexer_progress *stats) { unsigned int i; int error; @@ -1048,11 +1050,11 @@ static int resolve_deltas(git_indexer *idx, git_transfer_progress *stats) return 0; } -static int update_header_and_rehash(git_indexer *idx, git_transfer_progress *stats) +static int update_header_and_rehash(git_indexer *idx, git_indexer_progress *stats) { void *ptr; size_t chunk = 1024*1024; - git_off_t hashed = 0; + off64_t hashed = 0; git_mwindow *w = NULL; git_mwindow_file *mwf; unsigned int left; @@ -1089,7 +1091,7 @@ static int update_header_and_rehash(git_indexer *idx, git_transfer_progress *sta return 0; } -int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) +int git_indexer_commit(git_indexer *idx, git_indexer_progress *stats) { git_mwindow *w = NULL; unsigned int i, long_offsets = 0, left; @@ -1292,7 +1294,9 @@ on_error: void git_indexer_free(git_indexer *idx) { - size_t pos; + const git_oid *key; + git_oid *value; + size_t iter; if (idx == NULL) return; @@ -1321,14 +1325,9 @@ void git_indexer_free(git_indexer *idx) git_mutex_unlock(&git__mwindow_mutex); } - for (pos = git_oidmap_begin(idx->expected_oids); - pos != git_oidmap_end(idx->expected_oids); pos++) - { - if (git_oidmap_has_data(idx->expected_oids, pos)) { - git__free((git_oid *) git_oidmap_key(idx->expected_oids, pos)); - git_oidmap_delete_at(idx->expected_oids, pos); - } - } + iter = 0; + while (git_oidmap_iterate((void **) &value, idx->expected_oids, &iter, &key) == 0) + git__free(value); git_hash_ctx_cleanup(&idx->trailer); git_hash_ctx_cleanup(&idx->hash_ctx); diff --git a/src/integer.h b/src/integer.h index 10b109737..067c0be1f 100644 --- a/src/integer.h +++ b/src/integer.h @@ -8,10 +8,10 @@ #define INCLUDE_integer_h__ /** @return true if p fits into the range of a size_t */ -GIT_INLINE(int) git__is_sizet(git_off_t p) +GIT_INLINE(int) git__is_sizet(int64_t p) { size_t r = (size_t)p; - return p == (git_off_t)r; + return p == (int64_t)r; } /** @return true if p fits into the range of an ssize_t */ @@ -21,6 +21,13 @@ GIT_INLINE(int) git__is_ssizet(size_t p) return p == (size_t)r; } +/** @return true if p fits into the range of a uint16_t */ +GIT_INLINE(int) git__is_uint16(size_t p) +{ + uint16_t r = (uint16_t)p; + return p == (size_t)r; +} + /** @return true if p fits into the range of a uint32_t */ GIT_INLINE(int) git__is_uint32(size_t p) { @@ -29,10 +36,10 @@ GIT_INLINE(int) git__is_uint32(size_t p) } /** @return true if p fits into the range of an unsigned long */ -GIT_INLINE(int) git__is_ulong(git_off_t p) +GIT_INLINE(int) git__is_ulong(int64_t p) { unsigned long r = (unsigned long)p; - return p == (git_off_t)r; + return p == (int64_t)r; } /** @return true if p fits into the range of an int */ @@ -65,15 +72,25 @@ GIT_INLINE(int) git__is_int(long long p) # error compiler has add with overflow intrinsics but SIZE_MAX is unknown # endif +# define git__add_int_overflow(out, one, two) \ + __builtin_sadd_overflow(one, two, out) +# define git__sub_int_overflow(out, one, two) \ + __builtin_ssub_overflow(one, two, out) + /* Use Microsoft's safe integer handling functions where available */ #elif defined(_MSC_VER) +# define ENABLE_INTSAFE_SIGNED_FUNCTIONS # include # define git__add_sizet_overflow(out, one, two) \ (SizeTAdd(one, two, out) != S_OK) # define git__multiply_sizet_overflow(out, one, two) \ (SizeTMult(one, two, out) != S_OK) +#define git__add_int_overflow(out, one, two) \ + (IntAdd(one, two, out) != S_OK) +#define git__sub_int_overflow(out, one, two) \ + (IntSub(one, two, out) != S_OK) #else @@ -101,6 +118,24 @@ GIT_INLINE(bool) git__multiply_sizet_overflow(size_t *out, size_t one, size_t tw return false; } +GIT_INLINE(bool) git__add_int_overflow(int *out, int one, int two) +{ + if ((two > 0 && one > (INT_MAX - two)) || + (two < 0 && one < (INT_MIN - two))) + return true; + *out = one + two; + return false; +} + +GIT_INLINE(bool) git__sub_int_overflow(int *out, int one, int two) +{ + if ((two > 0 && one < (INT_MIN + two)) || + (two < 0 && one > (INT_MAX + two))) + return true; + *out = one - two; + return false; +} + #endif #endif diff --git a/src/iterator.c b/src/iterator.c index 0db929211..e26e2c0c7 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -145,7 +145,7 @@ static int iterator_init_common( (iter->flags & GIT_ITERATOR_PRECOMPOSE_UNICODE) == 0 && (iter->flags & GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE) == 0) { - if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0) + if (git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) < 0) git_error_clear(); else if (precompose) iter->flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; @@ -405,7 +405,7 @@ int git_iterator_for_nothing( iter = git__calloc(1, sizeof(empty_iterator)); GIT_ERROR_CHECK_ALLOC(iter); - iter->base.type = GIT_ITERATOR_TYPE_EMPTY; + iter->base.type = GIT_ITERATOR_EMPTY; iter->base.cb = &callbacks; iter->base.flags = options->flags; @@ -543,8 +543,6 @@ static int tree_iterator_frame_init( new_frame = git_array_alloc(iter->frames); GIT_ERROR_CHECK_ALLOC(new_frame); - memset(new_frame, 0, sizeof(tree_iterator_frame)); - if ((error = git_tree_dup(&dup, tree)) < 0) goto done; @@ -552,19 +550,22 @@ static int tree_iterator_frame_init( new_frame->tree = dup; if (frame_entry && - (error = tree_iterator_compute_path(&new_frame->path, frame_entry)) < 0) + (error = tree_iterator_compute_path(&new_frame->path, frame_entry)) < 0) goto done; cmp = iterator__ignore_case(&iter->base) ? tree_iterator_entry_sort_icase : NULL; - if ((error = git_vector_init( - &new_frame->entries, dup->entries.size, cmp)) < 0) + if ((error = git_vector_init(&new_frame->entries, + dup->entries.size, cmp)) < 0) goto done; git_array_foreach(dup->entries, i, tree_entry) { - new_entry = git_pool_malloc(&iter->entry_pool, 1); - GIT_ERROR_CHECK_ALLOC(new_entry); + if ((new_entry = git_pool_malloc(&iter->entry_pool, 1)) == NULL) { + git_error_set_oom(); + error = -1; + goto done; + } new_entry->tree_entry = tree_entry; new_entry->parent_path = new_frame->path.ptr; @@ -949,7 +950,7 @@ int git_iterator_for_tree( iter = git__calloc(1, sizeof(tree_iterator)); GIT_ERROR_CHECK_ALLOC(iter); - iter->base.type = GIT_ITERATOR_TYPE_TREE; + iter->base.type = GIT_ITERATOR_TREE; iter->base.cb = &callbacks; if ((error = iterator_init_common(&iter->base, @@ -973,7 +974,7 @@ int git_iterator_current_tree_entry( tree_iterator_frame *frame; tree_iterator_entry *entry; - assert(i->type == GIT_ITERATOR_TYPE_TREE); + assert(i->type == GIT_ITERATOR_TREE); iter = (tree_iterator *)i; @@ -990,7 +991,7 @@ int git_iterator_current_parent_tree( tree_iterator *iter; tree_iterator_frame *frame; - assert(i->type == GIT_ITERATOR_TYPE_TREE); + assert(i->type == GIT_ITERATOR_TREE); iter = (tree_iterator *)i; @@ -1270,7 +1271,7 @@ static int filesystem_iterator_entry_hash( return 0; } - if (iter->base.type == GIT_ITERATOR_TYPE_WORKDIR) + if (iter->base.type == GIT_ITERATOR_WORKDIR) return git_repository_hashfile(&entry->id, iter->base.repo, entry->path, GIT_OBJECT_BLOB, NULL); @@ -1521,7 +1522,7 @@ static void filesystem_iterator_set_current( static int filesystem_iterator_current( const git_index_entry **out, git_iterator *i) { - filesystem_iterator *iter = (filesystem_iterator *)i; + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); if (!iterator__has_been_accessed(i)) return iter->base.cb->advance(out, i); @@ -1568,7 +1569,7 @@ done: static int filesystem_iterator_advance( const git_index_entry **out, git_iterator *i) { - filesystem_iterator *iter = (filesystem_iterator *)i; + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); bool is_dir; int error = 0; @@ -1627,7 +1628,7 @@ static int filesystem_iterator_advance( static int filesystem_iterator_advance_into( const git_index_entry **out, git_iterator *i) { - filesystem_iterator *iter = (filesystem_iterator *)i; + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); filesystem_iterator_frame *frame; filesystem_iterator_entry *prev_entry; int error; @@ -1664,11 +1665,11 @@ static int filesystem_iterator_advance_into( int git_iterator_current_workdir_path(git_buf **out, git_iterator *i) { - filesystem_iterator *iter = (filesystem_iterator *)i; + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); const git_index_entry *entry; - if (i->type != GIT_ITERATOR_TYPE_FS && - i->type != GIT_ITERATOR_TYPE_WORKDIR) { + if (i->type != GIT_ITERATOR_FS && + i->type != GIT_ITERATOR_WORKDIR) { *out = NULL; return 0; } @@ -1724,18 +1725,22 @@ GIT_INLINE(bool) filesystem_iterator_current_is_ignored( bool git_iterator_current_is_ignored(git_iterator *i) { - if (i->type != GIT_ITERATOR_TYPE_WORKDIR) + filesystem_iterator *iter = NULL; + + if (i->type != GIT_ITERATOR_WORKDIR) return false; - return filesystem_iterator_current_is_ignored((filesystem_iterator *)i); + iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); + + return filesystem_iterator_current_is_ignored(iter); } bool git_iterator_current_tree_is_ignored(git_iterator *i) { - filesystem_iterator *iter = (filesystem_iterator *)i; + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); filesystem_iterator_frame *frame; - if (i->type != GIT_ITERATOR_TYPE_WORKDIR) + if (i->type != GIT_ITERATOR_WORKDIR) return false; frame = filesystem_iterator_current_frame(iter); @@ -1747,7 +1752,7 @@ static int filesystem_iterator_advance_over( git_iterator_status_t *status, git_iterator *i) { - filesystem_iterator *iter = (filesystem_iterator *)i; + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); filesystem_iterator_frame *current_frame; filesystem_iterator_entry *current_entry; const git_index_entry *entry = NULL; @@ -1866,7 +1871,7 @@ static int filesystem_iterator_init(filesystem_iterator *iter) static int filesystem_iterator_reset(git_iterator *i) { - filesystem_iterator *iter = (filesystem_iterator *)i; + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); filesystem_iterator_clear(iter); return filesystem_iterator_init(iter); @@ -1874,7 +1879,7 @@ static int filesystem_iterator_reset(git_iterator *i) static void filesystem_iterator_free(git_iterator *i) { - filesystem_iterator *iter = (filesystem_iterator *)i; + filesystem_iterator *iter = GIT_CONTAINER_OF(i, filesystem_iterator, base); git__free(iter->root); git_buf_dispose(&iter->current_path); git_tree_free(iter->tree); @@ -1889,7 +1894,7 @@ static int iterator_for_filesystem( const char *root, git_index *index, git_tree *tree, - git_iterator_type_t type, + git_iterator_t type, git_iterator_options *options) { filesystem_iterator *iter; @@ -1966,7 +1971,7 @@ int git_iterator_for_filesystem( git_iterator_options *options) { return iterator_for_filesystem(out, - NULL, root, NULL, NULL, GIT_ITERATOR_TYPE_FS, options); + NULL, root, NULL, NULL, GIT_ITERATOR_FS, options); } int git_iterator_for_workdir_ext( @@ -1994,7 +1999,7 @@ int git_iterator_for_workdir_ext( GIT_ITERATOR_IGNORE_DOT_GIT; return iterator_for_filesystem(out, - repo, repo_workdir, index, tree, GIT_ITERATOR_TYPE_WORKDIR, &options); + repo, repo_workdir, index, tree, GIT_ITERATOR_WORKDIR, &options); } @@ -2083,7 +2088,7 @@ static int index_iterator_skip_pseudotree(index_iterator *iter) static int index_iterator_advance( const git_index_entry **out, git_iterator *i) { - index_iterator *iter = (index_iterator *)i; + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); const git_index_entry *entry = NULL; bool is_submodule; int error = 0; @@ -2156,7 +2161,7 @@ static int index_iterator_advance( static int index_iterator_advance_into( const git_index_entry **out, git_iterator *i) { - index_iterator *iter = (index_iterator *)i; + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); if (! S_ISDIR(iter->tree_entry.mode)) { if (out) @@ -2174,7 +2179,7 @@ static int index_iterator_advance_over( git_iterator_status_t *status, git_iterator *i) { - index_iterator *iter = (index_iterator *)i; + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); const git_index_entry *entry; int error; @@ -2203,7 +2208,7 @@ static int index_iterator_init(index_iterator *iter) static int index_iterator_reset(git_iterator *i) { - index_iterator *iter = (index_iterator *)i; + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); index_iterator_clear(iter); return index_iterator_init(iter); @@ -2211,7 +2216,7 @@ static int index_iterator_reset(git_iterator *i) static void index_iterator_free(git_iterator *i) { - index_iterator *iter = (index_iterator *)i; + index_iterator *iter = GIT_CONTAINER_OF(i, index_iterator, base); git_index_snapshot_release(&iter->entries, iter->base.index); git_buf_dispose(&iter->tree_buf); @@ -2243,7 +2248,7 @@ int git_iterator_for_index( iter = git__calloc(1, sizeof(index_iterator)); GIT_ERROR_CHECK_ALLOC(iter); - iter->base.type = GIT_ITERATOR_TYPE_INDEX; + iter->base.type = GIT_ITERATOR_INDEX; iter->base.cb = &callbacks; if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0 || diff --git a/src/iterator.h b/src/iterator.h index bbe357fbd..ebd69362b 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -17,12 +17,12 @@ typedef struct git_iterator git_iterator; typedef enum { - GIT_ITERATOR_TYPE_EMPTY = 0, - GIT_ITERATOR_TYPE_TREE = 1, - GIT_ITERATOR_TYPE_INDEX = 2, - GIT_ITERATOR_TYPE_WORKDIR = 3, - GIT_ITERATOR_TYPE_FS = 4, -} git_iterator_type_t; + GIT_ITERATOR_EMPTY = 0, + GIT_ITERATOR_TREE = 1, + GIT_ITERATOR_INDEX = 2, + GIT_ITERATOR_WORKDIR = 3, + GIT_ITERATOR_FS = 4, +} git_iterator_t; typedef enum { /** ignore case for entry sort order */ @@ -78,7 +78,7 @@ typedef struct { } git_iterator_callbacks; struct git_iterator { - git_iterator_type_t type; + git_iterator_t type; git_iterator_callbacks *cb; git_repository *repo; @@ -238,7 +238,7 @@ GIT_INLINE(int) git_iterator_reset(git_iterator *iter) extern int git_iterator_reset_range( git_iterator *iter, const char *start, const char *end); -GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter) +GIT_INLINE(git_iterator_t) git_iterator_type(git_iterator *iter) { return iter->type; } diff --git a/src/map.h b/src/map.h index 2009c3ab5..6328d8cf4 100644 --- a/src/map.h +++ b/src/map.h @@ -40,7 +40,7 @@ typedef struct { /* memory mapped buffer */ assert((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \ assert((flags & GIT_MAP_FIXED) == 0); } while (0) -extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset); +extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset); extern int p_munmap(git_map *map); #endif diff --git a/src/merge.c b/src/merge.c index 14b76fa2e..afe69e564 100644 --- a/src/merge.c +++ b/src/merge.c @@ -68,6 +68,16 @@ struct merge_diff_df_data { git_merge_diff *prev_conflict; }; +/* + * This acts as a negative cache entry marker. In case we've tried to calculate + * similarity metrics for a given blob already but `git_hashsig` determined + * that it's too small in order to have a meaningful hash signature, we will + * insert the address of this marker instead of `NULL`. Like this, we can + * easily check whether we have checked a gien entry already and skip doing the + * calculation again and again. + */ +static int cache_invalid_marker; + /* Merge base computation */ int merge_bases_many(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, size_t length, const git_oid input_array[]) @@ -310,46 +320,55 @@ static int interesting(git_pqueue *list) return 0; } -static void clear_commit_marks_1(git_commit_list **plist, +static int clear_commit_marks_1(git_commit_list **plist, git_commit_list_node *commit, unsigned int mark) { while (commit) { unsigned int i; if (!(mark & commit->flags)) - return; + return 0; commit->flags &= ~mark; for (i = 1; i < commit->out_degree; i++) { git_commit_list_node *p = commit->parents[i]; - git_commit_list_insert(p, plist); + if (git_commit_list_insert(p, plist) == NULL) + return -1; } commit = commit->out_degree ? commit->parents[0] : NULL; } + + return 0; } -static void clear_commit_marks_many(git_vector *commits, unsigned int mark) +static int clear_commit_marks_many(git_vector *commits, unsigned int mark) { git_commit_list *list = NULL; git_commit_list_node *c; unsigned int i; git_vector_foreach(commits, i, c) { - git_commit_list_insert(c, &list); + if (git_commit_list_insert(c, &list) == NULL) + return -1; } while (list) - clear_commit_marks_1(&list, git_commit_list_pop(&list), mark); + if (clear_commit_marks_1(&list, git_commit_list_pop(&list), mark) < 0) + return -1; + return 0; } -static void clear_commit_marks(git_commit_list_node *commit, unsigned int mark) +static int clear_commit_marks(git_commit_list_node *commit, unsigned int mark) { git_commit_list *list = NULL; - git_commit_list_insert(commit, &list); + if (git_commit_list_insert(commit, &list) == NULL) + return -1; while (list) - clear_commit_marks_1(&list, git_commit_list_pop(&list), mark); + if (clear_commit_marks_1(&list, git_commit_list_pop(&list), mark) < 0) + return -1; + return 0; } static int paint_down_to_common( @@ -466,10 +485,11 @@ static int remove_redundant(git_revwalk *walk, git_vector *commits) redundant[filled_index[j]] = 1; } - clear_commit_marks(commit, ALL_FLAGS); - clear_commit_marks_many(&work, ALL_FLAGS); - git_commit_list_free(&common); + + if ((error = clear_commit_marks(commit, ALL_FLAGS)) < 0 || + (error = clear_commit_marks_many(&work, ALL_FLAGS)) < 0) + goto done; } for (i = 0; i < commits->length; ++i) { @@ -531,10 +551,9 @@ int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_l while (result) git_vector_insert(&redundant, git_commit_list_pop(&result)); - clear_commit_marks(one, ALL_FLAGS); - clear_commit_marks_many(twos, ALL_FLAGS); - - if ((error = remove_redundant(walk, &redundant)) < 0) { + if ((error = clear_commit_marks(one, ALL_FLAGS)) < 0 || + (error = clear_commit_marks_many(twos, ALL_FLAGS)) < 0 || + (error = remove_redundant(walk, &redundant)) < 0) { git_vector_free(&redundant); return error; } @@ -867,7 +886,7 @@ static int merge_conflict_invoke_driver( git_oid_cpy(&result->id, &oid); result->mode = mode; - result->file_size = buf.size; + result->file_size = (uint32_t)buf.size; result->path = git_pool_strdup(&diff_list->pool, path); GIT_ERROR_CHECK_ALLOC(result->path); @@ -1015,9 +1034,12 @@ static int index_entry_similarity_calc( { git_blob *blob; git_diff_file diff_file = {{{0}}}; - git_off_t blobsize; + git_object_size_t blobsize; int error; + if (*out || *out == &cache_invalid_marker) + return 0; + *out = NULL; if ((error = git_blob_lookup(&blob, repo, &entry->id)) < 0) @@ -1038,6 +1060,8 @@ static int index_entry_similarity_calc( error = opts->metric->buffer_signature(out, &diff_file, git_blob_rawcontent(blob), (size_t)blobsize, opts->metric->payload); + if (error == GIT_EBUFS) + *out = &cache_invalid_marker; git_blob_free(blob); @@ -1060,18 +1084,16 @@ static int index_entry_similarity_inexact( return 0; /* update signature cache if needed */ - if (!cache[a_idx] && (error = index_entry_similarity_calc(&cache[a_idx], repo, a, opts)) < 0) - return error; - if (!cache[b_idx] && (error = index_entry_similarity_calc(&cache[b_idx], repo, b, opts)) < 0) + if ((error = index_entry_similarity_calc(&cache[a_idx], repo, a, opts)) < 0 || + (error = index_entry_similarity_calc(&cache[b_idx], repo, b, opts)) < 0) return error; /* some metrics may not wish to process this file (too big / too small) */ - if (!cache[a_idx] || !cache[b_idx]) + if (cache[a_idx] == &cache_invalid_marker || cache[b_idx] == &cache_invalid_marker) return 0; /* compare signatures */ - if (opts->metric->similarity( - &score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0) + if (opts->metric->similarity(&score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0) return -1; /* clip score */ @@ -1104,14 +1126,12 @@ static void deletes_by_oid_free(git_oidmap *map) { git_oidmap_free(map); } -static int deletes_by_oid_enqueue(git_oidmap *map, git_pool* pool, const git_oid *id, size_t idx) { - size_t pos; +static int deletes_by_oid_enqueue(git_oidmap *map, git_pool* pool, const git_oid *id, size_t idx) +{ deletes_by_oid_queue *queue; size_t *array_entry; - int error; - pos = git_oidmap_lookup_index(map, id); - if (!git_oidmap_valid_index(map, pos)) { + if ((queue = git_oidmap_get(map, id)) == NULL) { queue = git_pool_malloc(pool, sizeof(deletes_by_oid_queue)); GIT_ERROR_CHECK_ALLOC(queue); @@ -1119,11 +1139,9 @@ static int deletes_by_oid_enqueue(git_oidmap *map, git_pool* pool, const git_oid queue->next_pos = 0; queue->first_entry = idx; - git_oidmap_insert(map, id, queue, &error); - if (error < 0) + if (git_oidmap_set(map, id, queue) < 0) return -1; } else { - queue = git_oidmap_value_at(map, pos); array_entry = git_array_alloc(queue->arr); GIT_ERROR_CHECK_ALLOC(array_entry); *array_entry = idx; @@ -1132,18 +1150,14 @@ static int deletes_by_oid_enqueue(git_oidmap *map, git_pool* pool, const git_oid return 0; } -static int deletes_by_oid_dequeue(size_t *idx, git_oidmap *map, const git_oid *id) { - size_t pos; +static int deletes_by_oid_dequeue(size_t *idx, git_oidmap *map, const git_oid *id) +{ deletes_by_oid_queue *queue; size_t *array_entry; - pos = git_oidmap_lookup_index(map, id); - - if (!git_oidmap_valid_index(map, pos)) + if ((queue = git_oidmap_get(map, id)) == NULL) return GIT_ENOTFOUND; - queue = git_oidmap_value_at(map, pos); - if (queue->next_pos == 0) { *idx = queue->first_entry; } else { @@ -1168,8 +1182,8 @@ static int merge_diff_mark_similarity_exact( git_oidmap *ours_deletes_by_oid = NULL, *theirs_deletes_by_oid = NULL; int error = 0; - if (!(ours_deletes_by_oid = git_oidmap_alloc()) || - !(theirs_deletes_by_oid = git_oidmap_alloc())) { + if (git_oidmap_new(&ours_deletes_by_oid) < 0 || + git_oidmap_new(&theirs_deletes_by_oid) < 0) { error = -1; goto done; } @@ -1549,7 +1563,7 @@ int git_merge_diff_list__find_renames( done: if (cache != NULL) { for (i = 0; i < cache_size; ++i) { - if (cache[i] != NULL) + if (cache[i] != NULL && cache[i] != &cache_invalid_marker) opts->metric->free_signature(cache[i], opts->metric->payload); } @@ -2430,7 +2444,7 @@ static int write_merge_head( assert(repo && heads); if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_HEAD_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0) + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0) goto cleanup; for (i = 0; i < heads_len; i++) { @@ -2458,7 +2472,7 @@ static int write_merge_mode(git_repository *repo) assert(repo); if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MODE_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0) + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0) goto cleanup; if ((error = git_filebuf_write(&file, "no-ff", 5)) < 0) @@ -2689,7 +2703,7 @@ static int write_merge_msg( entries[i].merge_head = heads[i]; if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) < 0 || (error = git_filebuf_write(&file, "Merge ", 6)) < 0) goto cleanup; @@ -3120,9 +3134,6 @@ static int merge_heads( *ancestor_head_out = NULL; *our_head_out = NULL; - if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0) - goto done; - if ((error = git_annotated_commit_from_ref(&our_head, repo, our_ref)) < 0) goto done; @@ -3333,24 +3344,40 @@ done: return error; } -int git_merge_init_options(git_merge_options *opts, unsigned int version) +int git_merge_options_init(git_merge_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_merge_options, GIT_MERGE_OPTIONS_INIT); return 0; } -int git_merge_file_init_input(git_merge_file_input *input, unsigned int version) +int git_merge_init_options(git_merge_options *opts, unsigned int version) +{ + return git_merge_options_init(opts, version); +} + +int git_merge_file_input_init(git_merge_file_input *input, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( input, version, git_merge_file_input, GIT_MERGE_FILE_INPUT_INIT); return 0; } -int git_merge_file_init_options( +int git_merge_file_init_input(git_merge_file_input *input, unsigned int version) +{ + return git_merge_file_input_init(input, version); +} + +int git_merge_file_options_init( git_merge_file_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_merge_file_options, GIT_MERGE_FILE_OPTIONS_INIT); return 0; } + +int git_merge_file_init_options( + git_merge_file_options *opts, unsigned int version) +{ + return git_merge_file_options_init(opts, version); +} diff --git a/src/merge.h b/src/merge.h index 173a1b435..b0a7de2be 100644 --- a/src/merge.h +++ b/src/merge.h @@ -84,7 +84,7 @@ typedef enum { /* The child of a folder that is in a directory/file conflict. */ GIT_MERGE_DIFF_DF_CHILD = (1 << 11), -} git_merge_diff_type_t; +} git_merge_diff_t; typedef struct { git_repository *repo; @@ -113,7 +113,7 @@ typedef struct { * Description of changes to one file across three trees. */ typedef struct { - git_merge_diff_type_t type; + git_merge_diff_t type; git_index_entry ancestor_entry; diff --git a/src/merge_driver.c b/src/merge_driver.c index 8b7e3559a..666349b15 100644 --- a/src/merge_driver.c +++ b/src/merge_driver.c @@ -32,7 +32,7 @@ static struct merge_driver_registry merge_driver_registry; static void git_merge_driver_global_shutdown(void); -const git_repository* git_merge_driver_source_repo(const git_merge_driver_source *src) +git_repository* git_merge_driver_source_repo(const git_merge_driver_source *src) { assert(src); return src->repo; @@ -371,17 +371,17 @@ static int merge_driver_name_for_path( return error; /* set: use the built-in 3-way merge driver ("text") */ - if (GIT_ATTR_TRUE(value)) + if (GIT_ATTR_IS_TRUE(value)) *out = merge_driver_name__text; /* unset: do not merge ("binary") */ - else if (GIT_ATTR_FALSE(value)) + else if (GIT_ATTR_IS_FALSE(value)) *out = merge_driver_name__binary; - else if (GIT_ATTR_UNSPECIFIED(value) && default_driver) + else if (GIT_ATTR_IS_UNSPECIFIED(value) && default_driver) *out = default_driver; - else if (GIT_ATTR_UNSPECIFIED(value)) + else if (GIT_ATTR_IS_UNSPECIFIED(value)) *out = merge_driver_name__text; else diff --git a/src/merge_file.c b/src/merge_file.c index 0647a5df6..23daa84c4 100644 --- a/src/merge_file.c +++ b/src/merge_file.c @@ -9,7 +9,7 @@ #include "repository.h" #include "posix.h" -#include "fileops.h" +#include "futils.h" #include "index.h" #include "diff_xdiff.h" #include "merge.h" diff --git a/src/mwindow.c b/src/mwindow.c index ffbee7d14..262786a5f 100644 --- a/src/mwindow.c +++ b/src/mwindow.c @@ -8,7 +8,7 @@ #include "mwindow.h" #include "vector.h" -#include "fileops.h" +#include "futils.h" #include "map.h" #include "global.h" #include "strmap.h" @@ -44,15 +44,14 @@ int git_mwindow_global_init(void) assert(!git__pack_cache); git__on_shutdown(git_mwindow_files_free); - return git_strmap_alloc(&git__pack_cache); + return git_strmap_new(&git__pack_cache); } int git_mwindow_get_pack(struct git_pack_file **out, const char *path) { - int error; - char *packname; - size_t pos; struct git_pack_file *pack; + char *packname; + int error; if ((error = git_packfile__name(&packname, path)) < 0) return error; @@ -62,13 +61,11 @@ int git_mwindow_get_pack(struct git_pack_file **out, const char *path) return -1; } - pos = git_strmap_lookup_index(git__pack_cache, packname); + pack = git_strmap_get(git__pack_cache, packname); git__free(packname); - if (git_strmap_valid_index(git__pack_cache, pos)) { - pack = git_strmap_value_at(git__pack_cache, pos); + if (pack != NULL) { git_atomic_inc(&pack->refcount); - git_mutex_unlock(&git__mwindow_mutex); *out = pack; return 0; @@ -82,7 +79,7 @@ int git_mwindow_get_pack(struct git_pack_file **out, const char *path) git_atomic_inc(&pack->refcount); - git_strmap_insert(git__pack_cache, pack->pack_name, pack, &error); + error = git_strmap_set(git__pack_cache, pack->pack_name, pack); git_mutex_unlock(&git__mwindow_mutex); if (error < 0) { @@ -97,7 +94,6 @@ int git_mwindow_get_pack(struct git_pack_file **out, const char *path) void git_mwindow_put_pack(struct git_pack_file *pack) { int count; - size_t pos; if (git_mutex_lock(&git__mwindow_mutex) < 0) return; @@ -105,13 +101,12 @@ void git_mwindow_put_pack(struct git_pack_file *pack) /* put before get would be a corrupted state */ assert(git__pack_cache); - pos = git_strmap_lookup_index(git__pack_cache, pack->pack_name); /* if we cannot find it, the state is corrupted */ - assert(git_strmap_valid_index(git__pack_cache, pos)); + assert(git_strmap_exists(git__pack_cache, pack->pack_name)); count = git_atomic_dec(&pack->refcount); if (count == 0) { - git_strmap_delete_at(git__pack_cache, pos); + git_strmap_delete(git__pack_cache, pack->pack_name); git_packfile_free(pack); } @@ -172,11 +167,11 @@ void git_mwindow_free_all_locked(git_mwindow_file *mwf) /* * Check if a window 'win' contains the address 'offset' */ -int git_mwindow_contains(git_mwindow *win, git_off_t offset) +int git_mwindow_contains(git_mwindow *win, off64_t offset) { - git_off_t win_off = win->offset; + off64_t win_off = win->offset; return win_off <= offset - && offset <= (git_off_t)(win_off + win->window_map.len); + && offset <= (off64_t)(win_off + win->window_map.len); } /* @@ -251,12 +246,12 @@ static int git_mwindow_close_lru(git_mwindow_file *mwf) static git_mwindow *new_window( git_mwindow_file *mwf, git_file fd, - git_off_t size, - git_off_t offset) + off64_t size, + off64_t offset) { git_mwindow_ctl *ctl = &mem_ctl; size_t walign = git_mwindow__window_size / 2; - git_off_t len; + off64_t len; git_mwindow *w; w = git__malloc(sizeof(*w)); @@ -268,8 +263,8 @@ static git_mwindow *new_window( w->offset = (offset / walign) * walign; len = size - w->offset; - if (len > (git_off_t)git_mwindow__window_size) - len = (git_off_t)git_mwindow__window_size; + if (len > (off64_t)git_mwindow__window_size) + len = (off64_t)git_mwindow__window_size; ctl->mapped += (size_t)len; @@ -316,7 +311,7 @@ static git_mwindow *new_window( unsigned char *git_mwindow_open( git_mwindow_file *mwf, git_mwindow **cursor, - git_off_t offset, + off64_t offset, size_t extra, unsigned int *left) { diff --git a/src/mwindow.h b/src/mwindow.h index ea962d1b6..1a391b055 100644 --- a/src/mwindow.h +++ b/src/mwindow.h @@ -16,7 +16,7 @@ typedef struct git_mwindow { struct git_mwindow *next; git_map window_map; - git_off_t offset; + off64_t offset; size_t last_used; size_t inuse_cnt; } git_mwindow; @@ -24,7 +24,7 @@ typedef struct git_mwindow { typedef struct git_mwindow_file { git_mwindow *windows; int fd; - git_off_t size; + off64_t size; } git_mwindow_file; typedef struct git_mwindow_ctl { @@ -37,10 +37,10 @@ typedef struct git_mwindow_ctl { git_vector windowfiles; } git_mwindow_ctl; -int git_mwindow_contains(git_mwindow *win, git_off_t offset); +int git_mwindow_contains(git_mwindow *win, off64_t offset); void git_mwindow_free_all(git_mwindow_file *mwf); /* locks */ void git_mwindow_free_all_locked(git_mwindow_file *mwf); /* run under lock */ -unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, size_t extra, unsigned int *left); +unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, off64_t offset, size_t extra, unsigned int *left); int git_mwindow_file_register(git_mwindow_file *mwf); void git_mwindow_file_deregister(git_mwindow_file *mwf); void git_mwindow_close(git_mwindow **w_cursor); diff --git a/src/net.c b/src/net.c new file mode 100644 index 000000000..d42fce52d --- /dev/null +++ b/src/net.c @@ -0,0 +1,411 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "net.h" +#include "netops.h" + +#include +#include "git2/errors.h" + +#include "posix.h" +#include "buffer.h" +#include "http_parser.h" +#include "global.h" + +#define DEFAULT_PORT_HTTP "80" +#define DEFAULT_PORT_HTTPS "443" +#define DEFAULT_PORT_GIT "9418" +#define DEFAULT_PORT_SSH "22" + +static const char *default_port_for_scheme(const char *scheme) +{ + if (strcmp(scheme, "http") == 0) + return DEFAULT_PORT_HTTP; + else if (strcmp(scheme, "https") == 0) + return DEFAULT_PORT_HTTPS; + else if (strcmp(scheme, "git") == 0) + return DEFAULT_PORT_GIT; + else if (strcmp(scheme, "ssh") == 0) + return DEFAULT_PORT_SSH; + + return NULL; +} + +int git_net_url_parse(git_net_url *url, const char *given) +{ + struct http_parser_url u = {0}; + bool has_scheme, has_host, has_port, has_path, has_query, has_userinfo; + git_buf scheme = GIT_BUF_INIT, + host = GIT_BUF_INIT, + port = GIT_BUF_INIT, + path = GIT_BUF_INIT, + username = GIT_BUF_INIT, + password = GIT_BUF_INIT, + query = GIT_BUF_INIT; + int error = GIT_EINVALIDSPEC; + + if (http_parser_parse_url(given, strlen(given), false, &u)) { + git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); + goto done; + } + + has_scheme = !!(u.field_set & (1 << UF_SCHEMA)); + has_host = !!(u.field_set & (1 << UF_HOST)); + has_port = !!(u.field_set & (1 << UF_PORT)); + has_path = !!(u.field_set & (1 << UF_PATH)); + has_query = !!(u.field_set & (1 << UF_QUERY)); + has_userinfo = !!(u.field_set & (1 << UF_USERINFO)); + + if (has_scheme) { + const char *url_scheme = given + u.field_data[UF_SCHEMA].off; + size_t url_scheme_len = u.field_data[UF_SCHEMA].len; + git_buf_put(&scheme, url_scheme, url_scheme_len); + git__strntolower(scheme.ptr, scheme.size); + } else { + git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); + goto done; + } + + if (has_host) { + const char *url_host = given + u.field_data[UF_HOST].off; + size_t url_host_len = u.field_data[UF_HOST].len; + git_buf_decode_percent(&host, url_host, url_host_len); + } + + if (has_port) { + const char *url_port = given + u.field_data[UF_PORT].off; + size_t url_port_len = u.field_data[UF_PORT].len; + git_buf_put(&port, url_port, url_port_len); + } else { + const char *default_port = default_port_for_scheme(scheme.ptr); + + if (default_port == NULL) { + git_error_set(GIT_ERROR_NET, "unknown scheme for URL '%s'", given); + goto done; + } + + git_buf_puts(&port, default_port); + } + + if (has_path) { + const char *url_path = given + u.field_data[UF_PATH].off; + size_t url_path_len = u.field_data[UF_PATH].len; + git_buf_put(&path, url_path, url_path_len); + } else { + git_buf_puts(&path, "/"); + } + + if (has_query) { + const char *url_query = given + u.field_data[UF_QUERY].off; + size_t url_query_len = u.field_data[UF_QUERY].len; + git_buf_decode_percent(&query, url_query, url_query_len); + } + + if (has_userinfo) { + const char *url_userinfo = given + u.field_data[UF_USERINFO].off; + size_t url_userinfo_len = u.field_data[UF_USERINFO].len; + const char *colon = memchr(url_userinfo, ':', url_userinfo_len); + + if (colon) { + const char *url_username = url_userinfo; + size_t url_username_len = colon - url_userinfo; + const char *url_password = colon + 1; + size_t url_password_len = url_userinfo_len - (url_username_len + 1); + + git_buf_decode_percent(&username, url_username, url_username_len); + git_buf_decode_percent(&password, url_password, url_password_len); + } else { + git_buf_decode_percent(&username, url_userinfo, url_userinfo_len); + } + } + + if (git_buf_oom(&scheme) || + git_buf_oom(&host) || + git_buf_oom(&port) || + git_buf_oom(&path) || + git_buf_oom(&query) || + git_buf_oom(&username) || + git_buf_oom(&password)) + return -1; + + url->scheme = git_buf_detach(&scheme); + url->host = git_buf_detach(&host); + url->port = git_buf_detach(&port); + url->path = git_buf_detach(&path); + url->query = git_buf_detach(&query); + url->username = git_buf_detach(&username); + url->password = git_buf_detach(&password); + + error = 0; + +done: + git_buf_dispose(&scheme); + git_buf_dispose(&host); + git_buf_dispose(&port); + git_buf_dispose(&path); + git_buf_dispose(&query); + git_buf_dispose(&username); + git_buf_dispose(&password); + return error; +} + +int git_net_url_joinpath( + git_net_url *out, + git_net_url *one, + const char *two) +{ + git_buf path = GIT_BUF_INIT; + const char *query; + size_t one_len, two_len; + + git_net_url_dispose(out); + + if ((query = strchr(two, '?')) != NULL) { + two_len = query - two; + + if (*(++query) != '\0') { + out->query = git__strdup(query); + GIT_ERROR_CHECK_ALLOC(out->query); + } + } else { + two_len = strlen(two); + } + + /* Strip all trailing `/`s from the first path */ + one_len = one->path ? strlen(one->path) : 0; + while (one_len && one->path[one_len - 1] == '/') + one_len--; + + /* Strip all leading `/`s from the second path */ + while (*two == '/') { + two++; + two_len--; + } + + git_buf_put(&path, one->path, one_len); + git_buf_putc(&path, '/'); + git_buf_put(&path, two, two_len); + + if (git_buf_oom(&path)) + return -1; + + out->path = git_buf_detach(&path); + + if (one->scheme) { + out->scheme = git__strdup(one->scheme); + GIT_ERROR_CHECK_ALLOC(out->scheme); + } + + if (one->host) { + out->host = git__strdup(one->host); + GIT_ERROR_CHECK_ALLOC(out->host); + } + + if (one->port) { + out->port = git__strdup(one->port); + GIT_ERROR_CHECK_ALLOC(out->port); + } + + if (one->username) { + out->username = git__strdup(one->username); + GIT_ERROR_CHECK_ALLOC(out->username); + } + + if (one->password) { + out->password = git__strdup(one->password); + GIT_ERROR_CHECK_ALLOC(out->password); + } + + return 0; +} + +/* + * Some servers strip the query parameters from the Location header + * when sending a redirect. Others leave it in place. + * Check for both, starting with the stripped case first, + * since it appears to be more common. + */ +static void remove_service_suffix( + git_net_url *url, + const char *service_suffix) +{ + const char *service_query = strchr(service_suffix, '?'); + size_t full_suffix_len = strlen(service_suffix); + size_t suffix_len = service_query ? + (size_t)(service_query - service_suffix) : full_suffix_len; + size_t path_len = strlen(url->path); + ssize_t truncate = -1; + + /* + * Check for a redirect without query parameters, + * like "/newloc/info/refs"' + */ + if (suffix_len && path_len >= suffix_len) { + size_t suffix_offset = path_len - suffix_len; + + if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 && + (!service_query || git__strcmp(url->query, service_query + 1) == 0)) { + truncate = suffix_offset; + } + } + + /* + * If we haven't already found where to truncate to remove the + * suffix, check for a redirect with query parameters, like + * "/newloc/info/refs?service=git-upload-pack" + */ + if (truncate < 0 && git__suffixcmp(url->path, service_suffix) == 0) + truncate = path_len - full_suffix_len; + + /* Ensure we leave a minimum of '/' as the path */ + if (truncate == 0) + truncate++; + + if (truncate > 0) { + url->path[truncate] = '\0'; + + git__free(url->query); + url->query = NULL; + } +} + +int git_net_url_apply_redirect( + git_net_url *url, + const char *redirect_location, + const char *service_suffix) +{ + git_net_url tmp = GIT_NET_URL_INIT; + int error = 0; + + assert(url && redirect_location); + + if (redirect_location[0] == '/') { + git__free(url->path); + + if ((url->path = git__strdup(redirect_location)) == NULL) { + error = -1; + goto done; + } + } else { + git_net_url *original = url; + + if ((error = git_net_url_parse(&tmp, redirect_location)) < 0) + goto done; + + /* Validate that this is a legal redirection */ + + if (original->scheme && + strcmp(original->scheme, tmp.scheme) != 0 && + strcmp(tmp.scheme, "https") != 0) { + git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", + original->scheme, tmp.scheme); + + error = -1; + goto done; + } + + if (original->host && + git__strcasecmp(original->host, tmp.host) != 0) { + git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", + original->host, tmp.host); + + error = -1; + goto done; + } + + git_net_url_swap(url, &tmp); + } + + /* Remove the service suffix if it was given to us */ + if (service_suffix) + remove_service_suffix(url, service_suffix); + +done: + git_net_url_dispose(&tmp); + return error; +} + +bool git_net_url_valid(git_net_url *url) +{ + return (url->host && url->port && url->path); +} + +int git_net_url_is_default_port(git_net_url *url) +{ + return (strcmp(url->port, default_port_for_scheme(url->scheme)) == 0); +} + +void git_net_url_swap(git_net_url *a, git_net_url *b) +{ + git_net_url tmp = GIT_NET_URL_INIT; + + memcpy(&tmp, a, sizeof(git_net_url)); + memcpy(a, b, sizeof(git_net_url)); + memcpy(b, &tmp, sizeof(git_net_url)); +} + +int git_net_url_fmt(git_buf *buf, git_net_url *url) +{ + git_buf_puts(buf, url->scheme); + git_buf_puts(buf, "://"); + + if (url->username) { + git_buf_puts(buf, url->username); + + if (url->password) { + git_buf_puts(buf, ":"); + git_buf_puts(buf, url->password); + } + + git_buf_putc(buf, '@'); + } + + git_buf_puts(buf, url->host); + + if (url->port && !git_net_url_is_default_port(url)) { + git_buf_putc(buf, ':'); + git_buf_puts(buf, url->port); + } + + git_buf_puts(buf, url->path ? url->path : "/"); + + if (url->query) { + git_buf_putc(buf, '?'); + git_buf_puts(buf, url->query); + } + + return git_buf_oom(buf) ? -1 : 0; +} + +int git_net_url_fmt_path(git_buf *buf, git_net_url *url) +{ + git_buf_puts(buf, url->path ? url->path : "/"); + + if (url->query) { + git_buf_putc(buf, '?'); + git_buf_puts(buf, url->query); + } + + return git_buf_oom(buf) ? -1 : 0; +} + +void git_net_url_dispose(git_net_url *url) +{ + if (url->username) + git__memzero(url->username, strlen(url->username)); + + if (url->password) + git__memzero(url->password, strlen(url->password)); + + git__free(url->scheme); url->scheme = NULL; + git__free(url->host); url->host = NULL; + git__free(url->port); url->port = NULL; + git__free(url->path); url->path = NULL; + git__free(url->query); url->query = NULL; + git__free(url->username); url->username = NULL; + git__free(url->password); url->password = NULL; +} diff --git a/src/net.h b/src/net.h new file mode 100644 index 000000000..7e72db13f --- /dev/null +++ b/src/net.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_net_h__ +#define INCLUDE_net_h__ + +#include "common.h" + +typedef struct git_net_url { + char *scheme; + char *host; + char *port; + char *path; + char *query; + char *username; + char *password; +} git_net_url; + +#define GIT_NET_URL_INIT { NULL } + +/** Parses a string containing a URL into a structure. */ +extern int git_net_url_parse(git_net_url *url, const char *str); + +/** Appends a path and/or query string to the given URL */ +extern int git_net_url_joinpath( + git_net_url *out, + git_net_url *in, + const char *path); + +/** Ensures that a URL is minimally valid (contains a host, port and path) */ +extern bool git_net_url_valid(git_net_url *url); + +/** Returns nonzero if the URL is on the default port. */ +extern int git_net_url_is_default_port(git_net_url *url); + +/* Applies a redirect to the URL with a git-aware service suffix. */ +extern int git_net_url_apply_redirect( + git_net_url *url, + const char *redirect_location, + const char *service_suffix); + +/** Swaps the contents of one URL for another. */ +extern void git_net_url_swap(git_net_url *a, git_net_url *b); + +/** Places the URL into the given buffer. */ +extern int git_net_url_fmt(git_buf *out, git_net_url *url); + +/** Place the path and query string into the given buffer. */ +extern int git_net_url_fmt_path(git_buf *buf, git_net_url *url); + +/** Disposes the contents of the structure. */ +extern void git_net_url_dispose(git_net_url *url); + +#endif diff --git a/src/netops.c b/src/netops.c index ecbc2aebe..04ae824cc 100644 --- a/src/netops.c +++ b/src/netops.c @@ -37,14 +37,17 @@ void gitno_buffer_setup_callback( static int recv_stream(gitno_buffer *buf) { git_stream *io = (git_stream *) buf->cb_data; - int ret; + size_t readlen = buf->len - buf->offset; + ssize_t ret; - ret = git_stream_read(io, buf->data + buf->offset, buf->len - buf->offset); + readlen = min(readlen, INT_MAX); + + ret = git_stream_read(io, buf->data + buf->offset, (int)readlen); if (ret < 0) return -1; buf->offset += ret; - return ret; + return (int)ret; } void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len) @@ -118,193 +121,3 @@ int gitno__match_host(const char *pattern, const char *host) return -1; } - -static const char *default_port_http = "80"; -static const char *default_port_https = "443"; - -const char *gitno__default_port( - gitno_connection_data *data) -{ - return data->use_ssl ? default_port_https : default_port_http; -} - -static const char *prefix_http = "http://"; -static const char *prefix_https = "https://"; - -int gitno_connection_data_from_url( - gitno_connection_data *data, - const char *url, - const char *service_suffix) -{ - int error = -1; - const char *default_port = NULL, *path_search_start = NULL; - char *original_host = NULL; - - /* service_suffix is optional */ - assert(data && url); - - /* Save these for comparison later */ - original_host = data->host; - data->host = NULL; - gitno_connection_data_free_ptrs(data); - - if (!git__prefixcmp(url, prefix_http)) { - path_search_start = url + strlen(prefix_http); - default_port = default_port_http; - - if (data->use_ssl) { - git_error_set(GIT_ERROR_NET, "redirect from HTTPS to HTTP is not allowed"); - goto cleanup; - } - } else if (!git__prefixcmp(url, prefix_https)) { - path_search_start = url + strlen(prefix_https); - default_port = default_port_https; - data->use_ssl = true; - } else if (url[0] == '/') - default_port = gitno__default_port(data); - - if (!default_port) { - git_error_set(GIT_ERROR_NET, "unrecognized URL prefix"); - goto cleanup; - } - - error = gitno_extract_url_parts( - &data->host, &data->port, &data->path, &data->user, &data->pass, - url, default_port); - - if (url[0] == '/') { - /* Relative redirect; reuse original host name and port */ - path_search_start = url; - git__free(data->host); - data->host = original_host; - original_host = NULL; - } - - if (!error) { - const char *path = strchr(path_search_start, '/'); - size_t pathlen = strlen(path); - size_t suffixlen = service_suffix ? strlen(service_suffix) : 0; - - if (suffixlen && - !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) { - git__free(data->path); - data->path = git__strndup(path, pathlen - suffixlen); - } else { - git__free(data->path); - data->path = git__strdup(path); - } - - /* Check for errors in the resulting data */ - if (original_host && url[0] != '/' && strcmp(original_host, data->host)) { - git_error_set(GIT_ERROR_NET, "cross host redirect not allowed"); - error = -1; - } - } - -cleanup: - if (original_host) git__free(original_host); - return error; -} - -void gitno_connection_data_free_ptrs(gitno_connection_data *d) -{ - git__free(d->host); d->host = NULL; - git__free(d->port); d->port = NULL; - git__free(d->path); d->path = NULL; - git__free(d->user); d->user = NULL; - git__free(d->pass); d->pass = NULL; -} - -int gitno_extract_url_parts( - char **host_out, - char **port_out, - char **path_out, - char **username_out, - char **password_out, - const char *url, - const char *default_port) -{ - struct http_parser_url u = {0}; - bool has_host, has_port, has_path, has_userinfo; - git_buf host = GIT_BUF_INIT, - port = GIT_BUF_INIT, - path = GIT_BUF_INIT, - username = GIT_BUF_INIT, - password = GIT_BUF_INIT; - int error = 0; - - if (http_parser_parse_url(url, strlen(url), false, &u)) { - git_error_set(GIT_ERROR_NET, "malformed URL '%s'", url); - error = GIT_EINVALIDSPEC; - goto done; - } - - has_host = !!(u.field_set & (1 << UF_HOST)); - has_port = !!(u.field_set & (1 << UF_PORT)); - has_path = !!(u.field_set & (1 << UF_PATH)); - has_userinfo = !!(u.field_set & (1 << UF_USERINFO)); - - if (has_host) { - const char *url_host = url + u.field_data[UF_HOST].off; - size_t url_host_len = u.field_data[UF_HOST].len; - git_buf_decode_percent(&host, url_host, url_host_len); - } - - if (has_port) { - const char *url_port = url + u.field_data[UF_PORT].off; - size_t url_port_len = u.field_data[UF_PORT].len; - git_buf_put(&port, url_port, url_port_len); - } else { - git_buf_puts(&port, default_port); - } - - if (has_path && path_out) { - const char *url_path = url + u.field_data[UF_PATH].off; - size_t url_path_len = u.field_data[UF_PATH].len; - git_buf_decode_percent(&path, url_path, url_path_len); - } else if (path_out) { - git_error_set(GIT_ERROR_NET, "invalid url, missing path"); - error = GIT_EINVALIDSPEC; - goto done; - } - - if (has_userinfo) { - const char *url_userinfo = url + u.field_data[UF_USERINFO].off; - size_t url_userinfo_len = u.field_data[UF_USERINFO].len; - const char *colon = memchr(url_userinfo, ':', url_userinfo_len); - - if (colon) { - const char *url_username = url_userinfo; - size_t url_username_len = colon - url_userinfo; - const char *url_password = colon + 1; - size_t url_password_len = url_userinfo_len - (url_username_len + 1); - - git_buf_decode_percent(&username, url_username, url_username_len); - git_buf_decode_percent(&password, url_password, url_password_len); - } else { - git_buf_decode_percent(&username, url_userinfo, url_userinfo_len); - } - } - - if (git_buf_oom(&host) || - git_buf_oom(&port) || - git_buf_oom(&path) || - git_buf_oom(&username) || - git_buf_oom(&password)) - return -1; - - *host_out = git_buf_detach(&host); - *port_out = git_buf_detach(&port); - if (path_out) - *path_out = git_buf_detach(&path); - *username_out = git_buf_detach(&username); - *password_out = git_buf_detach(&password); - -done: - git_buf_dispose(&host); - git_buf_dispose(&port); - git_buf_dispose(&path); - git_buf_dispose(&username); - git_buf_dispose(&password); - return error; -} diff --git a/src/netops.h b/src/netops.h index f376bd911..52f1cccb6 100644 --- a/src/netops.h +++ b/src/netops.h @@ -11,6 +11,7 @@ #include "posix.h" #include "stream.h" +#include "net.h" #ifdef GIT_OPENSSL # include @@ -64,38 +65,4 @@ int gitno_recv(gitno_buffer *buf); void gitno_consume(gitno_buffer *buf, const char *ptr); void gitno_consume_n(gitno_buffer *buf, size_t cons); -typedef struct gitno_connection_data { - char *host; - char *port; - char *path; - char *user; - char *pass; - bool use_ssl; -} gitno_connection_data; - -/* - * This replaces all the pointers in `data` with freshly-allocated strings, - * that the caller is responsible for freeing. - * `gitno_connection_data_free_ptrs` is good for this. - */ - -int gitno_connection_data_from_url( - gitno_connection_data *data, - const char *url, - const char *service_suffix); - -/* This frees all the pointers IN the struct, but not the struct itself. */ -void gitno_connection_data_free_ptrs(gitno_connection_data *data); - -int gitno_extract_url_parts( - char **host, - char **port, - char **path, - char **username, - char **password, - const char *url, - const char *default_port); - -const char *gitno__default_port(gitno_connection_data *data); - #endif diff --git a/src/notes.c b/src/notes.c index 8e622c64a..68d2ae9ec 100644 --- a/src/notes.c +++ b/src/notes.c @@ -288,7 +288,7 @@ static int note_write( /* TODO: should we apply filters? */ /* create note object */ - if ((error = git_blob_create_frombuffer(&oid, repo, note, strlen(note))) < 0) + if ((error = git_blob_create_from_buffer(&oid, repo, note, strlen(note))) < 0) goto cleanup; if ((error = manipulate_note_in_tree_r( @@ -320,7 +320,7 @@ static int note_new( git_blob *blob) { git_note *note = NULL; - git_off_t blobsize; + git_object_size_t blobsize; note = git__malloc(sizeof(git_note)); GIT_ERROR_CHECK_ALLOC(note); @@ -808,8 +808,11 @@ int git_note_next( git_oid_cpy(note_id, &item->id); - if (!(error = process_entry_path(item->path, annotated_id))) - git_iterator_advance(NULL, it); + if ((error = process_entry_path(item->path, annotated_id)) < 0) + return error; - return error; + if ((error = git_iterator_advance(NULL, it)) < 0 && error != GIT_ITEROVER) + return error; + + return 0; } diff --git a/src/object.c b/src/object.c index 15f2722c7..1b47ab1a6 100644 --- a/src/object.c +++ b/src/object.c @@ -21,6 +21,7 @@ bool git_object__strict_input_validation = true; extern int git_odb_hash(git_oid *out, const void *data, size_t len, git_object_t type); +size_t git_object__size(git_object_t type); typedef struct { const char *str; /* type name string */ @@ -200,7 +201,7 @@ int git_object_lookup_prefix( if (type != GIT_OBJECT_ANY && type != object->cached.type) { git_object_free(object); git_error_set(GIT_ERROR_INVALID, - "the requested type does not match the type in ODB"); + "the requested type does not match the type in the ODB"); return GIT_ENOTFOUND; } @@ -496,7 +497,7 @@ int git_object_short_id(git_buf *out, const git_object *obj) git_buf_sanitize(out); repo = git_object_owner(obj); - if ((error = git_repository__cvar(&len, repo, GIT_CVAR_ABBREV)) < 0) + if ((error = git_repository__configmap_lookup(&len, repo, GIT_CONFIGMAP_ABBREV)) < 0) return error; if ((error = git_repository_odb(&odb, repo)) < 0) @@ -549,4 +550,3 @@ bool git_object__is_valid( return true; } - diff --git a/src/object.h b/src/object.h index 227a6fdd5..4b6793612 100644 --- a/src/object.h +++ b/src/object.h @@ -11,6 +11,8 @@ #include "repository.h" +#define GIT_OBJECT_SIZE_MAX UINT64_MAX + extern bool git_object__strict_input_validation; /** Base git object for inheritance */ diff --git a/src/odb.c b/src/odb.c index 6a7e7ca7e..68d9a9a3f 100644 --- a/src/odb.c +++ b/src/odb.c @@ -10,7 +10,7 @@ #include #include "git2/object.h" #include "git2/sys/odb_backend.h" -#include "fileops.h" +#include "futils.h" #include "hash.h" #include "delta.h" #include "filter.h" @@ -89,7 +89,7 @@ int git_odb__format_object_header( size_t *written, char *hdr, size_t hdr_size, - git_off_t obj_len, + git_object_size_t obj_len, git_object_t obj_type) { const char *type_str = git_object_type2string(obj_type); @@ -320,20 +320,26 @@ int git_odb__hashlink(git_oid *out, const char *path) int git_odb_hashfile(git_oid *out, const char *path, git_object_t type) { - git_off_t size; - int result, fd = git_futils_open_ro(path); - if (fd < 0) + uint64_t size; + int fd, error = 0; + + if ((fd = git_futils_open_ro(path)) < 0) return fd; - if ((size = git_futils_filesize(fd)) < 0 || !git__is_sizet(size)) { + if ((error = git_futils_filesize(&size, fd)) < 0) + goto done; + + if (!git__is_sizet(size)) { git_error_set(GIT_ERROR_OS, "file size overflow for 32-bit systems"); - p_close(fd); - return -1; + error = -1; + goto done; } - result = git_odb__hashfd(out, fd, (size_t)size, type); + error = git_odb__hashfd(out, fd, (size_t)size, type); + +done: p_close(fd); - return result; + return error; } int git_odb_hash(git_oid *id, const void *data, size_t len, git_object_t type) @@ -385,7 +391,7 @@ static void fake_wstream__free(git_odb_stream *_stream) git__free(stream); } -static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend, git_off_t size, git_object_t type) +static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend, git_object_size_t size, git_object_t type) { fake_wstream *stream; size_t blobsize; @@ -448,7 +454,7 @@ int git_odb_new(git_odb **out) return -1; } if (git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) { - git_cache_free(&db->own_cache); + git_cache_dispose(&db->own_cache); git__free(db); return -1; } @@ -548,6 +554,7 @@ int git_odb__add_default_backends( #else if (p_stat(objects_dir, &st) < 0) { if (as_alternates) + /* this should warn */ return 0; git_error_set(GIT_ERROR_ODB, "failed to load object database in '%s'", objects_dir); @@ -665,7 +672,7 @@ int git_odb__set_caps(git_odb *odb, int caps) return -1; } - if (!git_repository__cvar(&val, repo, GIT_CVAR_FSYNCOBJECTFILES)) + if (!git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_FSYNCOBJECTFILES)) odb->do_fsync = !!val; } @@ -686,7 +693,7 @@ static void odb_free(git_odb *db) } git_vector_free(&db->backends); - git_cache_free(&db->own_cache); + git_cache_dispose(&db->own_cache); git__memzero(db, sizeof(*db)); git__free(db); @@ -766,7 +773,7 @@ int git_odb_exists(git_odb *db, const git_oid *id) assert(db && id); - if (git_oid_iszero(id)) + if (git_oid_is_zero(id)) return 0; if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { @@ -994,7 +1001,7 @@ int git_odb__read_header_or_object( *out = NULL; - if (git_oid_iszero(id)) + if (git_oid_is_zero(id)) return error_null_oid(GIT_ENOTFOUND, "cannot read object"); if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { @@ -1099,7 +1106,7 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) assert(out && db && id); - if (git_oid_iszero(id)) + if (git_oid_is_zero(id)) return error_null_oid(GIT_ENOTFOUND, "cannot read object"); *out = git_cache_get_raw(odb_cache(db), id); @@ -1123,7 +1130,7 @@ static int odb_otype_fast(git_object_t *type_p, git_odb *db, const git_oid *id) size_t _unused; int error; - if (git_oid_iszero(id)) + if (git_oid_is_zero(id)) return error_null_oid(GIT_ENOTFOUND, "cannot get object type"); if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { @@ -1283,7 +1290,7 @@ int git_odb_write( git_odb_hash(oid, data, len, type); - if (git_oid_iszero(oid)) + if (git_oid_is_zero(oid)) return error_null_oid(GIT_EINVALID, "cannot write object"); if (git_odb__freshen(db, oid)) @@ -1318,7 +1325,7 @@ int git_odb_write( return error; } -static int hash_header(git_hash_ctx *ctx, git_off_t size, git_object_t type) +static int hash_header(git_hash_ctx *ctx, git_object_size_t size, git_object_t type) { char header[64]; size_t hdrlen; @@ -1332,7 +1339,7 @@ static int hash_header(git_hash_ctx *ctx, git_off_t size, git_object_t type) } int git_odb_open_wstream( - git_odb_stream **stream, git_odb *db, git_off_t size, git_object_t type) + git_odb_stream **stream, git_odb *db, git_object_size_t size, git_object_t type) { size_t i, writes = 0; int error = GIT_ERROR; @@ -1468,7 +1475,7 @@ int git_odb_open_rstream( return error; } -int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer_progress_cb progress_cb, void *progress_payload) +int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_indexer_progress_cb progress_cb, void *progress_payload) { size_t i, writes = 0; int error = GIT_ERROR; @@ -1497,12 +1504,23 @@ int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer return error; } -void *git_odb_backend_malloc(git_odb_backend *backend, size_t len) +void *git_odb_backend_data_alloc(git_odb_backend *backend, size_t len) { GIT_UNUSED(backend); return git__malloc(len); } +void *git_odb_backend_malloc(git_odb_backend *backend, size_t len) +{ + return git_odb_backend_data_alloc(backend, len); +} + +void git_odb_backend_data_free(git_odb_backend *backend, void *data) +{ + GIT_UNUSED(backend); + git__free(data); +} + int git_odb_refresh(struct git_odb *db) { size_t i; diff --git a/src/odb.h b/src/odb.h index 8c73515f0..8dd4efd64 100644 --- a/src/odb.h +++ b/src/odb.h @@ -70,7 +70,8 @@ int git_odb__hashobj(git_oid *id, git_rawobj *obj); /* * Format the object header such as it would appear in the on-disk object */ -int git_odb__format_object_header(size_t *out_len, char *hdr, size_t hdr_size, git_off_t obj_len, git_object_t obj_type); +int git_odb__format_object_header(size_t *out_len, char *hdr, size_t hdr_size, git_object_size_t obj_len, git_object_t obj_type); + /* * Hash an open file descriptor. * This is a performance call when the contents of a fd need to be hashed, @@ -95,7 +96,7 @@ int git_odb__hashfd_filtered( * symlink, then the raw contents of the symlink will be hashed. Otherwise, * this will fallback to `git_odb__hashfd`. * - * The hash type for this call is always `GIT_OBJIECT_BLOB` because + * The hash type for this call is always `GIT_OBJECT_BLOB` because * symlinks may only point to blobs. */ int git_odb__hashlink(git_oid *out, const char *path); diff --git a/src/odb_loose.c b/src/odb_loose.c index 5bdf884d3..4a54b3fc0 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -10,7 +10,7 @@ #include #include "git2/object.h" #include "git2/sys/odb_backend.h" -#include "fileops.h" +#include "futils.h" #include "hash.h" #include "odb.h" #include "delta.h" @@ -408,7 +408,8 @@ done: static int read_header_loose(git_rawobj *out, git_buf *loc) { unsigned char obj[1024]; - int fd, obj_len, error; + ssize_t obj_len; + int fd, error; assert(out && loc); @@ -417,10 +418,14 @@ static int read_header_loose(git_rawobj *out, git_buf *loc) out->data = NULL; - if ((error = fd = git_futils_open_ro(loc->ptr)) < 0 || - (error = obj_len = p_read(fd, obj, sizeof(obj))) < 0) + if ((error = fd = git_futils_open_ro(loc->ptr)) < 0) goto done; + if ((obj_len = p_read(fd, obj, sizeof(obj))) < 0) { + error = (int)obj_len; + goto done; + } + if (!is_zlib_compressed_data(obj, (size_t)obj_len)) error = read_header_loose_packlike(out, obj, (size_t)obj_len); else @@ -819,7 +824,7 @@ static int filebuf_flags(loose_backend *backend) return flags; } -static int loose_backend__writestream(git_odb_stream **stream_out, git_odb_backend *_backend, git_off_t length, git_object_t type) +static int loose_backend__writestream(git_odb_stream **stream_out, git_odb_backend *_backend, git_object_size_t length, git_object_t type) { loose_backend *backend; loose_writestream *stream = NULL; @@ -828,7 +833,7 @@ static int loose_backend__writestream(git_odb_stream **stream_out, git_odb_backe size_t hdrlen; int error; - assert(_backend && length >= 0); + assert(_backend); backend = (loose_backend *)_backend; *stream_out = NULL; @@ -871,6 +876,8 @@ static int loose_backend__readstream_read( size_t start_remain = stream->start_len - stream->start_read; int total = 0, error; + buffer_len = min(buffer_len, INT_MAX); + /* * if we read more than just the header in the initial read, play * that back for the caller. @@ -882,20 +889,20 @@ static int loose_backend__readstream_read( buffer += chunk; stream->start_read += chunk; - total += chunk; + total += (int)chunk; buffer_len -= chunk; } if (buffer_len) { - size_t chunk = min(buffer_len, INT_MAX); + size_t chunk = buffer_len; if ((error = git_zstream_get_output(buffer, &chunk, &stream->zstream)) < 0) return error; - total += chunk; + total += (int)chunk; } - return total; + return (int)total; } static void loose_backend__readstream_free(git_odb_stream *_stream) diff --git a/src/odb_mempack.c b/src/odb_mempack.c index fb29dddd1..69c423bec 100644 --- a/src/odb_mempack.c +++ b/src/odb_mempack.c @@ -10,7 +10,7 @@ #include "git2/object.h" #include "git2/sys/odb_backend.h" #include "git2/sys/mempack.h" -#include "fileops.h" +#include "futils.h" #include "hash.h" #include "odb.h" #include "array.h" @@ -37,15 +37,9 @@ static int impl__write(git_odb_backend *_backend, const git_oid *oid, const void { struct memory_packer_db *db = (struct memory_packer_db *)_backend; struct memobject *obj = NULL; - size_t pos; size_t alloc_len; - int rval; - pos = git_oidmap_put(db->objects, oid, &rval); - if (rval < 0) - return -1; - - if (rval == 0) + if (git_oidmap_exists(db->objects, oid)) return 0; GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(struct memobject), len); @@ -57,8 +51,8 @@ static int impl__write(git_odb_backend *_backend, const git_oid *oid, const void obj->len = len; obj->type = type; - git_oidmap_set_key_at(db->objects, pos, &obj->oid); - git_oidmap_set_value_at(db->objects, pos, obj); + if (git_oidmap_set(db->objects, &obj->oid, obj) < 0) + return -1; if (type == GIT_OBJECT_COMMIT) { struct memobject **store = git_array_alloc(db->commits); @@ -79,15 +73,11 @@ static int impl__exists(git_odb_backend *backend, const git_oid *oid) static int impl__read(void **buffer_p, size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) { struct memory_packer_db *db = (struct memory_packer_db *)backend; - struct memobject *obj = NULL; - size_t pos; + struct memobject *obj; - pos = git_oidmap_lookup_index(db->objects, oid); - if (!git_oidmap_valid_index(db->objects, pos)) + if ((obj = git_oidmap_get(db->objects, oid)) == NULL) return GIT_ENOTFOUND; - obj = git_oidmap_value_at(db->objects, pos); - *len_p = obj->len; *type_p = obj->type; *buffer_p = git__malloc(obj->len); @@ -100,15 +90,11 @@ static int impl__read(void **buffer_p, size_t *len_p, git_object_t *type_p, git_ static int impl__read_header(size_t *len_p, git_object_t *type_p, git_odb_backend *backend, const git_oid *oid) { struct memory_packer_db *db = (struct memory_packer_db *)backend; - struct memobject *obj = NULL; - size_t pos; + struct memobject *obj; - pos = git_oidmap_lookup_index(db->objects, oid); - if (!git_oidmap_valid_index(db->objects, pos)) + if ((obj = git_oidmap_get(db->objects, oid)) == NULL) return GIT_ENOTFOUND; - obj = git_oidmap_value_at(db->objects, pos); - *len_p = obj->len; *type_p = obj->type; return 0; @@ -139,7 +125,7 @@ cleanup: return err; } -void git_mempack_reset(git_odb_backend *_backend) +int git_mempack_reset(git_odb_backend *_backend) { struct memory_packer_db *db = (struct memory_packer_db *)_backend; struct memobject *object = NULL; @@ -151,6 +137,8 @@ void git_mempack_reset(git_odb_backend *_backend) git_array_clear(db->commits); git_oidmap_clear(db->objects); + + return 0; } static void impl__free(git_odb_backend *_backend) @@ -171,7 +159,8 @@ int git_mempack_new(git_odb_backend **out) db = git__calloc(1, sizeof(struct memory_packer_db)); GIT_ERROR_CHECK_ALLOC(db); - db->objects = git_oidmap_alloc(); + if (git_oidmap_new(&db->objects) < 0) + return -1; db->parent.version = GIT_ODB_BACKEND_VERSION; db->parent.read = &impl__read; diff --git a/src/odb_pack.c b/src/odb_pack.c index beab37b7f..86c858df1 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -11,11 +11,10 @@ #include "git2/repository.h" #include "git2/indexer.h" #include "git2/sys/odb_backend.h" -#include "fileops.h" +#include "futils.h" #include "hash.h" #include "odb.h" #include "delta.h" -#include "sha1_lookup.h" #include "mwindow.h" #include "pack.h" @@ -485,7 +484,7 @@ static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb c return 0; } -static int pack_backend__writepack_append(struct git_odb_writepack *_writepack, const void *data, size_t size, git_transfer_progress *stats) +static int pack_backend__writepack_append(struct git_odb_writepack *_writepack, const void *data, size_t size, git_indexer_progress *stats) { struct pack_writepack *writepack = (struct pack_writepack *)_writepack; @@ -494,7 +493,7 @@ static int pack_backend__writepack_append(struct git_odb_writepack *_writepack, return git_indexer_append(writepack->indexer, data, size, stats); } -static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_transfer_progress *stats) +static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_indexer_progress *stats) { struct pack_writepack *writepack = (struct pack_writepack *)_writepack; @@ -516,7 +515,7 @@ static void pack_backend__writepack_free(struct git_odb_writepack *_writepack) static int pack_backend__writepack(struct git_odb_writepack **out, git_odb_backend *_backend, git_odb *odb, - git_transfer_progress_cb progress_cb, + git_indexer_progress_cb progress_cb, void *progress_payload) { git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; diff --git a/src/offmap.c b/src/offmap.c index d0fa9f031..be9eb66d8 100644 --- a/src/offmap.c +++ b/src/offmap.c @@ -14,13 +14,17 @@ #define kfree git__free #include "khash.h" -__KHASH_TYPE(off, git_off_t, void *) +__KHASH_TYPE(off, off64_t, void *) -__KHASH_IMPL(off, static kh_inline, git_off_t, void *, 1, kh_int64_hash_func, kh_int64_hash_equal) +__KHASH_IMPL(off, static kh_inline, off64_t, void *, 1, kh_int64_hash_func, kh_int64_hash_equal) -git_offmap *git_offmap_alloc(void) + +int git_offmap_new(git_offmap **out) { - return kh_init(off); + *out = kh_init(off); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; } void git_offmap_free(git_offmap *map) @@ -33,81 +37,65 @@ void git_offmap_clear(git_offmap *map) kh_clear(off, map); } -size_t git_offmap_num_entries(git_offmap *map) +size_t git_offmap_size(git_offmap *map) { return kh_size(map); } -size_t git_offmap_lookup_index(git_offmap *map, const git_off_t key) +void *git_offmap_get(git_offmap *map, const off64_t key) { - return kh_get(off, map, key); + size_t idx = kh_get(off, map, key); + if (idx == kh_end(map) || !kh_exist(map, idx)) + return NULL; + return kh_val(map, idx); } -int git_offmap_valid_index(git_offmap *map, size_t idx) +int git_offmap_set(git_offmap *map, const off64_t key, void *value) { - return idx != kh_end(map); + size_t idx; + int rval; + + idx = kh_put(off, map, key, &rval); + if (rval < 0) + return -1; + + if (rval == 0) + kh_key(map, idx) = key; + + kh_val(map, idx) = value; + + return 0; } -int git_offmap_exists(git_offmap *map, const git_off_t key) +int git_offmap_delete(git_offmap *map, const off64_t key) +{ + khiter_t idx = kh_get(off, map, key); + if (idx == kh_end(map)) + return GIT_ENOTFOUND; + kh_del(off, map, idx); + return 0; +} + +int git_offmap_exists(git_offmap *map, const off64_t key) { return kh_get(off, map, key) != kh_end(map); } -int git_offmap_has_data(git_offmap *map, size_t idx) +int git_offmap_iterate(void **value, git_offmap *map, size_t *iter, off64_t *key) { - return kh_exist(map, idx); -} + size_t i = *iter; -git_off_t git_offmap_key_at(git_offmap *map, size_t idx) -{ - return kh_key(map, idx); -} + while (i < map->n_buckets && !kh_exist(map, i)) + i++; -void *git_offmap_value_at(git_offmap *map, size_t idx) -{ - return kh_val(map, idx); -} + if (i >= map->n_buckets) + return GIT_ITEROVER; -void git_offmap_set_value_at(git_offmap *map, size_t idx, void *value) -{ - kh_val(map, idx) = value; -} + if (key) + *key = kh_key(map, i); + if (value) + *value = kh_value(map, i); + *iter = ++i; -void git_offmap_delete_at(git_offmap *map, size_t idx) -{ - kh_del(off, map, idx); -} - -int git_offmap_put(git_offmap *map, const git_off_t key, int *err) -{ - return kh_put(off, map, key, err); -} - -void git_offmap_insert(git_offmap *map, const git_off_t key, void *value, int *rval) -{ - khiter_t idx = kh_put(off, map, key, rval); - - if ((*rval) >= 0) { - if ((*rval) == 0) - kh_key(map, idx) = key; - kh_val(map, idx) = value; - } -} - -void git_offmap_delete(git_offmap *map, const git_off_t key) -{ - khiter_t idx = git_offmap_lookup_index(map, key); - if (git_offmap_valid_index(map, idx)) - git_offmap_delete_at(map, idx); -} - -size_t git_offmap_begin(git_offmap *map) -{ - GIT_UNUSED(map); return 0; } - -size_t git_offmap_end(git_offmap *map) -{ - return map->n_buckets; -} diff --git a/src/offmap.h b/src/offmap.h index c68809389..81c459b01 100644 --- a/src/offmap.h +++ b/src/offmap.h @@ -11,44 +11,122 @@ #include "git2/types.h" +/** A map with `off64_t`s as key. */ typedef struct kh_off_s git_offmap; -git_offmap *git_offmap_alloc(void); +/** + * Allocate a new `off64_t` map. + * + * @param out Pointer to the map that shall be allocated. + * @return 0 on success, an error code if allocation has failed. + */ +int git_offmap_new(git_offmap **out); + +/** + * Free memory associated with the map. + * + * Note that this function will _not_ free values added to this + * map. + * + * @param map Pointer to the map that is to be free'd. May be + * `NULL`. + */ void git_offmap_free(git_offmap *map); + +/** + * Clear all entries from the map. + * + * This function will remove all entries from the associated map. + * Memory associated with it will not be released, though. + * + * @param map Pointer to the map that shall be cleared. May be + * `NULL`. + */ void git_offmap_clear(git_offmap *map); -size_t git_offmap_num_entries(git_offmap *map); +/** + * Return the number of elements in the map. + * + * @parameter map map containing the elements + * @return number of elements in the map + */ +size_t git_offmap_size(git_offmap *map); -size_t git_offmap_lookup_index(git_offmap *map, const git_off_t key); -int git_offmap_valid_index(git_offmap *map, size_t idx); +/** + * Return value associated with the given key. + * + * @param map map to search key in + * @param key key to search for + * @return value associated with the given key or NULL if the key was not found + */ +void *git_offmap_get(git_offmap *map, const off64_t key); -int git_offmap_exists(git_offmap *map, const git_off_t key); -int git_offmap_has_data(git_offmap *map, size_t idx); +/** + * Set the entry for key to value. + * + * If the map has no corresponding entry for the given key, a new + * entry will be created with the given value. If an entry exists + * already, its value will be updated to match the given value. + * + * @param map map to create new entry in + * @param key key to set + * @param value value to associate the key with; may be NULL + * @return zero if the key was successfully set, a negative error + * code otherwise + */ +int git_offmap_set(git_offmap *map, const off64_t key, void *value); -git_off_t git_offmap_key_at(git_offmap *map, size_t idx); -void *git_offmap_value_at(git_offmap *map, size_t idx); -void git_offmap_set_value_at(git_offmap *map, size_t idx, void *value); -void git_offmap_delete_at(git_offmap *map, size_t idx); +/** + * Delete an entry from the map. + * + * Delete the given key and its value from the map. If no such + * key exists, this will do nothing. + * + * @param map map to delete key in + * @param key key to delete + * @return `0` if the key has been deleted, GIT_ENOTFOUND if no + * such key was found, a negative code in case of an + * error + */ +int git_offmap_delete(git_offmap *map, const off64_t key); -int git_offmap_put(git_offmap *map, const git_off_t key, int *err); -void git_offmap_insert(git_offmap *map, const git_off_t key, void *value, int *rval); -void git_offmap_delete(git_offmap *map, const git_off_t key); +/** + * Check whether a key exists in the given map. + * + * @param map map to query for the key + * @param key key to search for + * @return 0 if the key has not been found, 1 otherwise + */ +int git_offmap_exists(git_offmap *map, const off64_t key); -size_t git_offmap_begin(git_offmap *map); -size_t git_offmap_end(git_offmap *map); +/** + * Iterate over entries of the map. + * + * This functions allows to iterate over all key-value entries of + * the map. The current position is stored in the `iter` variable + * and should be initialized to `0` before the first call to this + * function. + * + * @param map map to iterate over + * @param value pointer to the variable where to store the current + * value. May be NULL. + * @param iter iterator storing the current position. Initialize + * with zero previous to the first call. + * @param key pointer to the variable where to store the current + * key. May be NULL. + * @return `0` if the next entry was correctly retrieved. + * GIT_ITEROVER if no entries are left. A negative error + * code otherwise. + */ +int git_offmap_iterate(void **value, git_offmap *map, size_t *iter, off64_t *key); -#define git_offmap_foreach(h, kvar, vvar, code) { size_t __i; \ - for (__i = git_offmap_begin(h); __i != git_offmap_end(h); ++__i) { \ - if (!git_offmap_has_data(h,__i)) continue; \ - (kvar) = git_offmap_key_at(h,__i); \ - (vvar) = git_offmap_value_at(h,__i); \ +#define git_offmap_foreach(h, kvar, vvar, code) { size_t __i = 0; \ + while (git_offmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ code; \ } } -#define git_offmap_foreach_value(h, vvar, code) { size_t __i; \ - for (__i = git_offmap_begin(h); __i != git_offmap_end(h); ++__i) { \ - if (!git_offmap_has_data(h,__i)) continue; \ - (vvar) = git_offmap_value_at(h,__i); \ +#define git_offmap_foreach_value(h, vvar, code) { size_t __i = 0; \ + while (git_offmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ code; \ } } diff --git a/src/oid.c b/src/oid.c index 8d45e4d7d..641956652 100644 --- a/src/oid.c +++ b/src/oid.c @@ -64,13 +64,13 @@ GIT_INLINE(char) *fmt_one(char *str, unsigned int val) return str; } -void git_oid_nfmt(char *str, size_t n, const git_oid *oid) +int git_oid_nfmt(char *str, size_t n, const git_oid *oid) { size_t i, max_i; if (!oid) { memset(str, 0, n); - return; + return 0; } if (n > GIT_OID_HEXSZ) { memset(&str[GIT_OID_HEXSZ], 0, n - GIT_OID_HEXSZ); @@ -84,14 +84,16 @@ void git_oid_nfmt(char *str, size_t n, const git_oid *oid) if (n & 1) *str++ = to_hex[oid->id[i] >> 4]; + + return 0; } -void git_oid_fmt(char *str, const git_oid *oid) +int git_oid_fmt(char *str, const git_oid *oid) { - git_oid_nfmt(str, GIT_OID_HEXSZ, oid); + return git_oid_nfmt(str, GIT_OID_HEXSZ, oid); } -void git_oid_pathfmt(char *str, const git_oid *oid) +int git_oid_pathfmt(char *str, const git_oid *oid) { size_t i; @@ -99,6 +101,8 @@ void git_oid_pathfmt(char *str, const git_oid *oid) *str++ = '/'; for (i = 1; i < sizeof(oid->id); i++) str = fmt_one(str, oid->id[i]); + + return 0; } char *git_oid_tostr_s(const git_oid *oid) @@ -167,14 +171,16 @@ void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid) git_buf_putc(buf, '\n'); } -void git_oid_fromraw(git_oid *out, const unsigned char *raw) +int git_oid_fromraw(git_oid *out, const unsigned char *raw) { memcpy(out->id, raw, sizeof(out->id)); + return 0; } -void git_oid_cpy(git_oid *out, const git_oid *src) +int git_oid_cpy(git_oid *out, const git_oid *src) { memcpy(out->id, src->id, sizeof(out->id)); + return 0; } int git_oid_cmp(const git_oid *a, const git_oid *b) @@ -237,7 +243,7 @@ int git_oid_streq(const git_oid *oid_a, const char *str) return git_oid_strcmp(oid_a, str) == 0 ? 0 : -1; } -int git_oid_iszero(const git_oid *oid_a) +int git_oid_is_zero(const git_oid *oid_a) { const unsigned char *a = oid_a->id; unsigned int i; @@ -247,6 +253,11 @@ int git_oid_iszero(const git_oid *oid_a) return 1; } +int git_oid_iszero(const git_oid *oid_a) +{ + return git_oid_is_zero(oid_a); +} + typedef short node_index; typedef union { diff --git a/src/oidmap.c b/src/oidmap.c index c42e5c25a..0ae8bf33e 100644 --- a/src/oidmap.c +++ b/src/oidmap.c @@ -25,9 +25,12 @@ GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid) __KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, git_oidmap_hash, git_oid_equal) -git_oidmap *git_oidmap_alloc() +int git_oidmap_new(git_oidmap **out) { - return kh_init(oid); + *out = kh_init(oid); + GIT_ERROR_CHECK_ALLOC(*out); + + return 0; } void git_oidmap_free(git_oidmap *map) @@ -45,14 +48,38 @@ size_t git_oidmap_size(git_oidmap *map) return kh_size(map); } -size_t git_oidmap_lookup_index(git_oidmap *map, const git_oid *key) +void *git_oidmap_get(git_oidmap *map, const git_oid *key) { - return kh_get(oid, map, key); + size_t idx = kh_get(oid, map, key); + if (idx == kh_end(map) || !kh_exist(map, idx)) + return NULL; + return kh_val(map, idx); } -int git_oidmap_valid_index(git_oidmap *map, size_t idx) +int git_oidmap_set(git_oidmap *map, const git_oid *key, void *value) { - return idx != kh_end(map); + size_t idx; + int rval; + + idx = kh_put(oid, map, key, &rval); + if (rval < 0) + return -1; + + if (rval == 0) + kh_key(map, idx) = key; + + kh_val(map, idx) = value; + + return 0; +} + +int git_oidmap_delete(git_oidmap *map, const git_oid *key) +{ + khiter_t idx = kh_get(oid, map, key); + if (idx == kh_end(map)) + return GIT_ENOTFOUND; + kh_del(oid, map, idx); + return 0; } int git_oidmap_exists(git_oidmap *map, const git_oid *key) @@ -60,66 +87,21 @@ int git_oidmap_exists(git_oidmap *map, const git_oid *key) return kh_get(oid, map, key) != kh_end(map); } -int git_oidmap_has_data(git_oidmap *map, size_t idx) +int git_oidmap_iterate(void **value, git_oidmap *map, size_t *iter, const git_oid **key) { - return kh_exist(map, idx); -} + size_t i = *iter; -const git_oid *git_oidmap_key(git_oidmap *map, size_t idx) -{ - return kh_key(map, idx); -} + while (i < map->n_buckets && !kh_exist(map, i)) + i++; -void git_oidmap_set_key_at(git_oidmap *map, size_t idx, git_oid *key) -{ - kh_key(map, idx) = key; -} + if (i >= map->n_buckets) + return GIT_ITEROVER; -void *git_oidmap_value_at(git_oidmap *map, size_t idx) -{ - return kh_val(map, idx); -} + if (key) + *key = kh_key(map, i); + if (value) + *value = kh_value(map, i); + *iter = ++i; -void git_oidmap_set_value_at(git_oidmap *map, size_t idx, void *value) -{ - kh_val(map, idx) = value; -} - -void git_oidmap_delete_at(git_oidmap *map, size_t idx) -{ - kh_del(oid, map, idx); -} - -int git_oidmap_put(git_oidmap *map, const git_oid *key, int *err) -{ - return kh_put(oid, map, key, err); -} - -void git_oidmap_insert(git_oidmap *map, const git_oid *key, void *value, int *rval) -{ - khiter_t idx = kh_put(oid, map, key, rval); - - if ((*rval) >= 0) { - if ((*rval) == 0) - kh_key(map, idx) = key; - kh_val(map, idx) = value; - } -} - -void git_oidmap_delete(git_oidmap *map, const git_oid *key) -{ - khiter_t idx = git_oidmap_lookup_index(map, key); - if (git_oidmap_valid_index(map, idx)) - git_oidmap_delete_at(map, idx); -} - -size_t git_oidmap_begin(git_oidmap *map) -{ - GIT_UNUSED(map); return 0; } - -size_t git_oidmap_end(git_oidmap *map) -{ - return map->n_buckets; -} diff --git a/src/oidmap.h b/src/oidmap.h index a417907bd..b748f727c 100644 --- a/src/oidmap.h +++ b/src/oidmap.h @@ -11,37 +11,117 @@ #include "git2/oid.h" +/** A map with `git_oid`s as key. */ typedef struct kh_oid_s git_oidmap; -git_oidmap *git_oidmap_alloc(void); +/** + * Allocate a new OID map. + * + * @param out Pointer to the map that shall be allocated. + * @return 0 on success, an error code if allocation has failed. + */ +int git_oidmap_new(git_oidmap **out); + +/** + * Free memory associated with the map. + * + * Note that this function will _not_ free values added to this + * map. + * + * @param map Pointer to the map that is to be free'd. May be + * `NULL`. + */ void git_oidmap_free(git_oidmap *map); + +/** + * Clear all entries from the map. + * + * This function will remove all entries from the associated map. + * Memory associated with it will not be released, though. + * + * @param map Pointer to the map that shall be cleared. May be + * `NULL`. + */ void git_oidmap_clear(git_oidmap *map); +/** + * Return the number of elements in the map. + * + * @parameter map map containing the elements + * @return number of elements in the map + */ size_t git_oidmap_size(git_oidmap *map); -size_t git_oidmap_lookup_index(git_oidmap *map, const git_oid *key); -int git_oidmap_valid_index(git_oidmap *map, size_t idx); +/** + * Return value associated with the given key. + * + * @param map map to search key in + * @param key key to search for + * @return value associated with the given key or NULL if the key was not found + */ +void *git_oidmap_get(git_oidmap *map, const git_oid *key); +/** + * Set the entry for key to value. + * + * If the map has no corresponding entry for the given key, a new + * entry will be created with the given value. If an entry exists + * already, its value will be updated to match the given value. + * + * @param map map to create new entry in + * @param key key to set + * @param value value to associate the key with; may be NULL + * @return zero if the key was successfully set, a negative error + * code otherwise + */ +int git_oidmap_set(git_oidmap *map, const git_oid *key, void *value); + +/** + * Delete an entry from the map. + * + * Delete the given key and its value from the map. If no such + * key exists, this will do nothing. + * + * @param map map to delete key in + * @param key key to delete + * @return `0` if the key has been deleted, GIT_ENOTFOUND if no + * such key was found, a negative code in case of an + * error + */ +int git_oidmap_delete(git_oidmap *map, const git_oid *key); + +/** + * Check whether a key exists in the given map. + * + * @param map map to query for the key + * @param key key to search for + * @return 0 if the key has not been found, 1 otherwise + */ int git_oidmap_exists(git_oidmap *map, const git_oid *key); -int git_oidmap_has_data(git_oidmap *map, size_t idx); -const git_oid *git_oidmap_key(git_oidmap *map, size_t idx); -void git_oidmap_set_key_at(git_oidmap *map, size_t idx, git_oid *key); -void *git_oidmap_value_at(git_oidmap *map, size_t idx); -void git_oidmap_set_value_at(git_oidmap *map, size_t idx, void *value); -void git_oidmap_delete_at(git_oidmap *map, size_t idx); +/** + * Iterate over entries of the map. + * + * This functions allows to iterate over all key-value entries of + * the map. The current position is stored in the `iter` variable + * and should be initialized to `0` before the first call to this + * function. + * + * @param map map to iterate over + * @param value pointer to the variable where to store the current + * value. May be NULL. + * @param iter iterator storing the current position. Initialize + * with zero previous to the first call. + * @param key pointer to the variable where to store the current + * key. May be NULL. + * @return `0` if the next entry was correctly retrieved. + * GIT_ITEROVER if no entries are left. A negative error + * code otherwise. + */ +int git_oidmap_iterate(void **value, git_oidmap *map, size_t *iter, const git_oid **key); -int git_oidmap_put(git_oidmap *map, const git_oid *key, int *err); -void git_oidmap_insert(git_oidmap *map, const git_oid *key, void *value, int *rval); -void git_oidmap_delete(git_oidmap *map, const git_oid *key); - -size_t git_oidmap_begin(git_oidmap *map); -size_t git_oidmap_end(git_oidmap *map); - -#define git_oidmap_foreach_value(h, vvar, code) { size_t __i; \ - for (__i = git_oidmap_begin(h); __i != git_oidmap_end(h); ++__i) { \ - if (!git_oidmap_has_data(h,__i)) continue; \ - (vvar) = git_oidmap_value_at(h,__i); \ +#define git_oidmap_foreach_value(h, vvar, code) { size_t __i = 0; \ + while (git_oidmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ code; \ } } diff --git a/src/pack-objects.c b/src/pack-objects.c index e1528646a..49b4e4772 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -38,7 +38,7 @@ struct tree_walk_context { struct pack_write_context { git_indexer *indexer; - git_transfer_progress *stats; + git_indexer_progress *stats; }; struct walk_object { @@ -141,12 +141,10 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo) pb = git__calloc(1, sizeof(*pb)); GIT_ERROR_CHECK_ALLOC(pb); - pb->object_ix = git_oidmap_alloc(); - if (!pb->object_ix) + if (git_oidmap_new(&pb->object_ix) < 0) goto on_error; - pb->walk_objects = git_oidmap_alloc(); - if (!pb->walk_objects) + if (git_oidmap_new(&pb->walk_objects) < 0) goto on_error; git_pool_init(&pb->object_pool, sizeof(struct walk_object)); @@ -194,24 +192,26 @@ unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n) return pb->nr_threads; } -static void rehash(git_packbuilder *pb) +static int rehash(git_packbuilder *pb) { git_pobject *po; - size_t pos, i; - int ret; + size_t i; git_oidmap_clear(pb->object_ix); + for (i = 0, po = pb->object_list; i < pb->nr_objects; i++, po++) { - pos = git_oidmap_put(pb->object_ix, &po->id, &ret); - git_oidmap_set_value_at(pb->object_ix, pos, po); + if (git_oidmap_set(pb->object_ix, &po->id, po) < 0) + return -1; } + + return 0; } int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, const char *name) { git_pobject *po; - size_t newsize, pos; + size_t newsize; int ret; assert(pb && oid); @@ -223,7 +223,7 @@ int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, if (pb->nr_objects >= pb->nr_alloc) { GIT_ERROR_CHECK_ALLOC_ADD(&newsize, pb->nr_alloc, 1024); - GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newsize, newsize, 3 / 2); + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&newsize, newsize / 2, 3); if (!git__is_uint32(newsize)) { git_error_set(GIT_ERROR_NOMEMORY, "packfile too large to fit in memory."); @@ -235,7 +235,9 @@ int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, pb->object_list = git__reallocarray(pb->object_list, pb->nr_alloc, sizeof(*po)); GIT_ERROR_CHECK_ALLOC(pb->object_list); - rehash(pb); + + if (rehash(pb) < 0) + return -1; } po = pb->object_list + pb->nr_objects; @@ -248,13 +250,10 @@ int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, git_oid_cpy(&po->id, oid); po->hash = name_hash(name); - pos = git_oidmap_put(pb->object_ix, &po->id, &ret); - if (ret < 0) { + if (git_oidmap_set(pb->object_ix, &po->id, po) < 0) { git_error_set_oom(); - return ret; + return -1; } - assert(ret != 0); - git_oidmap_set_value_at(pb->object_ix, pos, po); pb->done = false; @@ -375,7 +374,9 @@ static int write_object( GIT_ERROR_CHECK_ALLOC(zbuf); git_zstream_reset(&pb->zstream); - git_zstream_set_input(&pb->zstream, data, data_len); + + if ((error = git_zstream_set_input(&pb->zstream, data, data_len)) < 0) + goto done; while (!git_zstream_done(&pb->zstream)) { if ((error = git_zstream_get_output(zbuf, &zbuf_len, &pb->zstream)) < 0 || @@ -514,15 +515,12 @@ static int cb_tag_foreach(const char *name, git_oid *oid, void *data) { git_packbuilder *pb = data; git_pobject *po; - size_t pos; GIT_UNUSED(name); - pos = git_oidmap_lookup_index(pb->object_ix, oid); - if (!git_oidmap_valid_index(pb->object_ix, pos)) + if ((po = git_oidmap_get(pb->object_ix, oid)) == NULL) return 0; - po = git_oidmap_value_at(pb->object_ix, pos); po->tagged = 1; /* TODO: peel objects */ @@ -1383,12 +1381,12 @@ int git_packbuilder_write( git_packbuilder *pb, const char *path, unsigned int mode, - git_transfer_progress_cb progress_cb, + git_indexer_progress_cb progress_cb, void *progress_cb_payload) { git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; git_indexer *indexer; - git_transfer_progress stats; + git_indexer_progress stats; struct pack_write_context ctx; int t; @@ -1401,7 +1399,7 @@ int git_packbuilder_write( &indexer, path, mode, pb->odb, &opts) < 0) return -1; - if (!git_repository__cvar(&t, pb->repo, GIT_CVAR_FSYNCOBJECTFILES) && t) + if (!git_repository__configmap_lookup(&t, pb->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) git_indexer__set_fsync(indexer, 1); ctx.indexer = indexer; @@ -1539,18 +1537,15 @@ static int lookup_walk_object(struct walk_object **out, git_packbuilder *pb, con static int retrieve_object(struct walk_object **out, git_packbuilder *pb, const git_oid *id) { - int error; - size_t pos; struct walk_object *obj; + int error; - pos = git_oidmap_lookup_index(pb->walk_objects, id); - if (git_oidmap_valid_index(pb->walk_objects, pos)) { - obj = git_oidmap_value_at(pb->walk_objects, pos); - } else { + if ((obj = git_oidmap_get(pb->walk_objects, id)) == NULL) { if ((error = lookup_walk_object(&obj, pb, id)) < 0) return error; - git_oidmap_insert(pb->walk_objects, &obj->id, obj, &error); + if ((error = git_oidmap_set(pb->walk_objects, &obj->id, obj)) < 0) + return error; } *out = obj; diff --git a/src/pack-objects.h b/src/pack-objects.h index 53684a1ba..04514daa6 100644 --- a/src/pack-objects.h +++ b/src/pack-objects.h @@ -30,7 +30,7 @@ typedef struct git_pobject { git_oid id; git_object_t type; - git_off_t offset; + off64_t offset; size_t size; diff --git a/src/pack.c b/src/pack.c index ef360a90b..fcf64f57d 100644 --- a/src/pack.c +++ b/src/pack.c @@ -7,22 +7,22 @@ #include "pack.h" -#include "odb.h" #include "delta.h" -#include "sha1_lookup.h" +#include "futils.h" #include "mwindow.h" -#include "fileops.h" +#include "odb.h" #include "oid.h" -#include +/* Option to bypass checking existence of '.keep' files */ +bool git_disable_pack_keep_file_checks = false; static int packfile_open(struct git_pack_file *p); -static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n); +static off64_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n); static int packfile_unpack_compressed( git_rawobj *obj, struct git_pack_file *p, git_mwindow **w_curs, - git_off_t *curpos, + off64_t *curpos, size_t size, git_object_t type); @@ -34,7 +34,7 @@ static int packfile_unpack_compressed( * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. */ static int pack_entry_find_offset( - git_off_t *offset_out, + off64_t *offset_out, git_oid *found_oid, struct git_pack_file *p, const git_oid *short_oid, @@ -89,8 +89,8 @@ static void cache_free(git_pack_cache *cache) static int cache_init(git_pack_cache *cache) { - cache->entries = git_offmap_alloc(); - GIT_ERROR_CHECK_ALLOC(cache->entries); + if (git_offmap_new(&cache->entries) < 0) + return -1; cache->memory_limit = GIT_PACK_CACHE_MEMORY_LIMIT; @@ -106,17 +106,14 @@ static int cache_init(git_pack_cache *cache) return 0; } -static git_pack_cache_entry *cache_get(git_pack_cache *cache, git_off_t offset) +static git_pack_cache_entry *cache_get(git_pack_cache *cache, off64_t offset) { - git_pack_cache_entry *entry = NULL; - size_t k; + git_pack_cache_entry *entry; if (git_mutex_lock(&cache->lock) < 0) return NULL; - k = git_offmap_lookup_index(cache->entries, offset); - if (git_offmap_valid_index(cache->entries, k)) { /* found it */ - entry = git_offmap_value_at(cache->entries, k); + if ((entry = git_offmap_get(cache->entries, offset)) != NULL) { git_atomic_inc(&entry->refcount); entry->last_usage = cache->use_ctr++; } @@ -128,7 +125,7 @@ static git_pack_cache_entry *cache_get(git_pack_cache *cache, git_off_t offset) /* Run with the cache lock held */ static void free_lowest_entry(git_pack_cache *cache) { - git_off_t offset; + off64_t offset; git_pack_cache_entry *entry; git_offmap_foreach(cache->entries, offset, entry, { @@ -144,11 +141,10 @@ static int cache_add( git_pack_cache_entry **cached_out, git_pack_cache *cache, git_rawobj *base, - git_off_t offset) + off64_t offset) { git_pack_cache_entry *entry; - int error, exists = 0; - size_t k; + int exists; if (base->len > GIT_PACK_CACHE_SIZE_LIMIT) return -1; @@ -166,9 +162,7 @@ static int cache_add( while (cache->memory_used + base->len > cache->memory_limit) free_lowest_entry(cache); - k = git_offmap_put(cache->entries, offset, &error); - assert(error != 0); - git_offmap_set_value_at(cache->entries, k, entry); + git_offmap_set(cache->entries, offset, entry); cache->memory_used += entry->raw.len; *cached_out = entry; @@ -348,7 +342,7 @@ static int pack_index_open(struct git_pack_file *p) static unsigned char *pack_window_open( struct git_pack_file *p, git_mwindow **w_cursor, - git_off_t offset, + off64_t offset, unsigned int *left) { if (p->mwf.fd == -1 && packfile_open(p) < 0) @@ -444,7 +438,7 @@ int git_packfile_unpack_header( git_object_t *type_p, git_mwindow_file *mwf, git_mwindow **w_curs, - git_off_t *curpos) + off64_t *curpos) { unsigned char *base; unsigned int left; @@ -477,13 +471,13 @@ int git_packfile_resolve_header( size_t *size_p, git_object_t *type_p, struct git_pack_file *p, - git_off_t offset) + off64_t offset) { git_mwindow *w_curs = NULL; - git_off_t curpos = offset; + off64_t curpos = offset; size_t size; git_object_t type; - git_off_t base_offset; + off64_t base_offset; int error; error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); @@ -531,13 +525,13 @@ int git_packfile_resolve_header( * cache, we stop calculating there. */ static int pack_dependency_chain(git_dependency_chain *chain_out, - git_pack_cache_entry **cached_out, git_off_t *cached_off, + git_pack_cache_entry **cached_out, off64_t *cached_off, struct pack_chain_elem *small_stack, size_t *stack_sz, - struct git_pack_file *p, git_off_t obj_offset) + struct git_pack_file *p, off64_t obj_offset) { git_dependency_chain chain = GIT_ARRAY_INIT; git_mwindow *w_curs = NULL; - git_off_t curpos = obj_offset, base_offset; + off64_t curpos = obj_offset, base_offset; int error = 0, use_heap = 0; size_t size, elem_pos; git_object_t type; @@ -622,10 +616,10 @@ on_error: int git_packfile_unpack( git_rawobj *obj, struct git_pack_file *p, - git_off_t *obj_offset) + off64_t *obj_offset) { git_mwindow *w_curs = NULL; - git_off_t curpos = *obj_offset; + off64_t curpos = *obj_offset; int error, free_base = 0; git_dependency_chain chain = GIT_ARRAY_INIT; struct pack_chain_elem *elem = NULL, *stack; @@ -768,31 +762,13 @@ cleanup: return error; } -static void *use_git_alloc(void *opaq, unsigned int count, unsigned int size) +int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, off64_t curpos) { - GIT_UNUSED(opaq); - return git__calloc(count, size); -} - -static void use_git_free(void *opaq, void *ptr) -{ - GIT_UNUSED(opaq); - git__free(ptr); -} - -int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, git_off_t curpos) -{ - int st; - memset(obj, 0, sizeof(git_packfile_stream)); obj->curpos = curpos; obj->p = p; - obj->zstream.zalloc = use_git_alloc; - obj->zstream.zfree = use_git_free; - obj->zstream.next_in = Z_NULL; - obj->zstream.next_out = Z_NULL; - st = inflateInit(&obj->zstream); - if (st != Z_OK) { + + if (git_zstream_init(&obj->zstream, GIT_ZSTREAM_INFLATE) < 0) { git_error_set(GIT_ERROR_ZLIB, "failed to init packfile stream"); return -1; } @@ -802,126 +778,116 @@ int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len) { + unsigned int window_len; unsigned char *in; - size_t written; - int st; + int error; if (obj->done) return 0; - in = pack_window_open(obj->p, &obj->mw, obj->curpos, &obj->zstream.avail_in); - if (in == NULL) + if ((in = pack_window_open(obj->p, &obj->mw, obj->curpos, &window_len)) == NULL) return GIT_EBUFS; - obj->zstream.next_out = buffer; - obj->zstream.avail_out = (unsigned int)len; - obj->zstream.next_in = in; - - st = inflate(&obj->zstream, Z_SYNC_FLUSH); - git_mwindow_close(&obj->mw); - - obj->curpos += obj->zstream.next_in - in; - written = len - obj->zstream.avail_out; - - if (st != Z_OK && st != Z_STREAM_END) { + if ((error = git_zstream_set_input(&obj->zstream, in, window_len)) < 0 || + (error = git_zstream_get_output_chunk(buffer, &len, &obj->zstream)) < 0) { + git_mwindow_close(&obj->mw); git_error_set(GIT_ERROR_ZLIB, "error reading from the zlib stream"); return -1; } - if (st == Z_STREAM_END) + git_mwindow_close(&obj->mw); + + obj->curpos += window_len - obj->zstream.in_len; + + if (git_zstream_eos(&obj->zstream)) obj->done = 1; - /* If we didn't write anything out but we're not done, we need more data */ - if (!written && st != Z_STREAM_END) + if (!len && !git_zstream_eos(&obj->zstream)) return GIT_EBUFS; - return written; + return len; } void git_packfile_stream_dispose(git_packfile_stream *obj) { - inflateEnd(&obj->zstream); + git_zstream_free(&obj->zstream); } static int packfile_unpack_compressed( git_rawobj *obj, struct git_pack_file *p, - git_mwindow **w_curs, - git_off_t *curpos, + git_mwindow **mwindow, + off64_t *position, size_t size, git_object_t type) { - size_t buf_size; - int st; - z_stream stream; - unsigned char *buffer, *in; + git_zstream zstream = GIT_ZSTREAM_INIT; + size_t buffer_len, total = 0; + char *data = NULL; + int error; - GIT_ERROR_CHECK_ALLOC_ADD(&buf_size, size, 1); - buffer = git__calloc(1, buf_size); - GIT_ERROR_CHECK_ALLOC(buffer); + GIT_ERROR_CHECK_ALLOC_ADD(&buffer_len, size, 1); + data = git__calloc(1, buffer_len); + GIT_ERROR_CHECK_ALLOC(data); - memset(&stream, 0, sizeof(stream)); - stream.next_out = buffer; - stream.avail_out = (uInt)buf_size; - stream.zalloc = use_git_alloc; - stream.zfree = use_git_free; - - st = inflateInit(&stream); - if (st != Z_OK) { - git__free(buffer); + if ((error = git_zstream_init(&zstream, GIT_ZSTREAM_INFLATE)) < 0) { git_error_set(GIT_ERROR_ZLIB, "failed to init zlib stream on unpack"); - - return -1; + goto out; } do { - in = pack_window_open(p, w_curs, *curpos, &stream.avail_in); - stream.next_in = in; - st = inflate(&stream, Z_FINISH); - git_mwindow_close(w_curs); + size_t bytes = buffer_len - total; + unsigned int window_len; + unsigned char *in; - if (!stream.avail_out) - break; /* the payload is larger than it should be */ + in = pack_window_open(p, mwindow, *position, &window_len); - if (st == Z_BUF_ERROR && in == NULL) { - inflateEnd(&stream); - git__free(buffer); - return GIT_EBUFS; + if ((error = git_zstream_set_input(&zstream, in, window_len)) < 0 || + (error = git_zstream_get_output_chunk(data + total, &bytes, &zstream)) < 0) { + git_mwindow_close(mwindow); + goto out; } - *curpos += stream.next_in - in; - } while (st == Z_OK || st == Z_BUF_ERROR); + git_mwindow_close(mwindow); - inflateEnd(&stream); + *position += window_len - zstream.in_len; + total += bytes; + } while (total < size); - if ((st != Z_STREAM_END) || stream.total_out != size) { - git__free(buffer); + if (total != size || !git_zstream_eos(&zstream)) { git_error_set(GIT_ERROR_ZLIB, "error inflating zlib stream"); - return -1; + error = -1; + goto out; } obj->type = type; obj->len = size; - obj->data = buffer; - return 0; + obj->data = data; + +out: + git_zstream_free(&zstream); + if (error) + git__free(data); + + return error; } /* * curpos is where the data starts, delta_obj_offset is the where the * header starts */ -git_off_t get_delta_base( +off64_t get_delta_base( struct git_pack_file *p, git_mwindow **w_curs, - git_off_t *curpos, + off64_t *curpos, git_object_t type, - git_off_t delta_obj_offset) + off64_t delta_obj_offset) { unsigned int left = 0; unsigned char *base_info; - git_off_t base_offset; + off64_t base_offset; git_oid unused; base_info = pack_window_open(p, w_curs, *curpos, &left); @@ -954,14 +920,13 @@ git_off_t get_delta_base( } else if (type == GIT_OBJECT_REF_DELTA) { /* If we have the cooperative cache, search in it first */ if (p->has_cache) { + struct git_pack_entry *entry; git_oid oid; - size_t k; git_oid_fromraw(&oid, base_info); - k = git_oidmap_lookup_index(p->idx_cache, &oid); - if (git_oidmap_valid_index(p->idx_cache, k)) { + if ((entry = git_oidmap_get(p->idx_cache, &oid)) != NULL) { *curpos += 20; - return ((struct git_pack_entry *)git_oidmap_value_at(p->idx_cache, k))->offset; + return entry->offset; } else { /* If we're building an index, don't try to find the pack * entry; we just haven't seen it yet. We'll make @@ -1049,7 +1014,7 @@ static int packfile_open(struct git_pack_file *p) if (!p->mwf.size) { if (!S_ISREG(st.st_mode)) goto cleanup; - p->mwf.size = (git_off_t)st.st_size; + p->mwf.size = (off64_t)st.st_size; } else if (p->mwf.size != st.st_size) goto cleanup; @@ -1141,9 +1106,11 @@ int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) if (git__suffixcmp(path, ".idx") == 0) { size_t root_len = path_len - strlen(".idx"); - memcpy(p->pack_name + root_len, ".keep", sizeof(".keep")); - if (git_path_exists(p->pack_name) == true) - p->pack_keep = 1; + if (!git_disable_pack_keep_file_checks) { + memcpy(p->pack_name + root_len, ".keep", sizeof(".keep")); + if (git_path_exists(p->pack_name) == true) + p->pack_keep = 1; + } memcpy(p->pack_name + root_len, ".pack", sizeof(".pack")); } @@ -1184,7 +1151,7 @@ int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) * ***********************************************************/ -static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n) +static off64_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n) { const unsigned char *index = p->index_map.data; const unsigned char *end = index + p->index_map.len; @@ -1271,8 +1238,29 @@ int git_pack_foreach_entry( return error; } +static int sha1_position(const void *table, size_t stride, unsigned lo, + unsigned hi, const unsigned char *key) +{ + const unsigned char *base = table; + + while (lo < hi) { + unsigned mi = (lo + hi) / 2; + int cmp = git_oid__hashcmp(base + mi * stride, key); + + if (!cmp) + return mi; + + if (cmp > 0) + hi = mi; + else + lo = mi+1; + } + + return -((int)lo)-1; +} + static int pack_entry_find_offset( - git_off_t *offset_out, + off64_t *offset_out, git_oid *found_oid, struct git_pack_file *p, const git_oid *short_oid, @@ -1282,7 +1270,7 @@ static int pack_entry_find_offset( const unsigned char *index; unsigned hi, lo, stride; int pos, found = 0; - git_off_t offset; + off64_t offset; const unsigned char *current = 0; *offset_out = 0; @@ -1377,7 +1365,7 @@ int git_pack_entry_find( const git_oid *short_oid, size_t len) { - git_off_t offset; + off64_t offset; git_oid found_oid; int error; diff --git a/src/pack.h b/src/pack.h index f21ad9fa3..a6a32ff20 100644 --- a/src/pack.h +++ b/src/pack.h @@ -10,15 +10,15 @@ #include "common.h" -#include - #include "git2/oid.h" +#include "array.h" #include "map.h" #include "mwindow.h" #include "odb.h" +#include "offmap.h" #include "oidmap.h" -#include "array.h" +#include "zstream.h" #define GIT_PACK_FILE_MODE 0444 @@ -63,17 +63,14 @@ typedef struct git_pack_cache_entry { } git_pack_cache_entry; struct pack_chain_elem { - git_off_t base_key; - git_off_t offset; + off64_t base_key; + off64_t offset; size_t size; git_object_t type; }; typedef git_array_t(struct pack_chain_elem) git_dependency_chain; -#include "offmap.h" -#include "oidmap.h" - #define GIT_PACK_CACHE_MEMORY_LIMIT 16 * 1024 * 1024 #define GIT_PACK_CACHE_SIZE_LIMIT 1024 * 1024 /* don't bother caching anything over 1MB */ @@ -110,15 +107,15 @@ struct git_pack_file { }; struct git_pack_entry { - git_off_t offset; + off64_t offset; git_oid sha1; struct git_pack_file *p; }; typedef struct git_packfile_stream { - git_off_t curpos; + off64_t curpos; int done; - z_stream zstream; + git_zstream zstream; struct git_pack_file *p; git_mwindow *mw; } git_packfile_stream; @@ -132,23 +129,23 @@ int git_packfile_unpack_header( git_object_t *type_p, git_mwindow_file *mwf, git_mwindow **w_curs, - git_off_t *curpos); + off64_t *curpos); int git_packfile_resolve_header( size_t *size_p, git_object_t *type_p, struct git_pack_file *p, - git_off_t offset); + off64_t offset); -int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, git_off_t *obj_offset); +int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, off64_t *obj_offset); -int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, git_off_t curpos); +int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, off64_t curpos); ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len); void git_packfile_stream_dispose(git_packfile_stream *obj); -git_off_t get_delta_base(struct git_pack_file *p, git_mwindow **w_curs, - git_off_t *curpos, git_object_t type, - git_off_t delta_obj_offset); +off64_t get_delta_base(struct git_pack_file *p, git_mwindow **w_curs, + off64_t *curpos, git_object_t type, + off64_t delta_obj_offset); void git_packfile_close(struct git_pack_file *p, bool unlink_packfile); void git_packfile_free(struct git_pack_file *p); diff --git a/src/parse.c b/src/parse.c index b04fda36b..0a10758bf 100644 --- a/src/parse.c +++ b/src/parse.c @@ -101,6 +101,16 @@ int git_parse_advance_digit(int64_t *out, git_parse_ctx *ctx, int base) return 0; } +int git_parse_advance_oid(git_oid *out, git_parse_ctx *ctx) +{ + if (ctx->line_len < GIT_OID_HEXSZ) + return -1; + if ((git_oid_fromstrn(out, ctx->line, GIT_OID_HEXSZ)) < 0) + return -1; + git_parse_advance_chars(ctx, GIT_OID_HEXSZ); + return 0; +} + int git_parse_peek(char *out, git_parse_ctx *ctx, int flags) { size_t remain = ctx->line_len; diff --git a/src/parse.h b/src/parse.h index 21dcf9bd1..0ecb7c103 100644 --- a/src/parse.h +++ b/src/parse.h @@ -23,12 +23,11 @@ typedef struct { size_t line_num; } git_parse_ctx; +#define GIT_PARSE_CTX_INIT { 0 } + int git_parse_ctx_init(git_parse_ctx *ctx, const char *content, size_t content_len); void git_parse_ctx_clear(git_parse_ctx *ctx); -#define git_parse_err(...) \ - ( git_error_set(GIT_ERROR_PATCH, __VA_ARGS__), -1 ) - #define git_parse_ctx_contains_s(ctx, str) \ git_parse_ctx_contains(ctx, str, sizeof(str) - 1) @@ -51,6 +50,7 @@ int git_parse_advance_expected( int git_parse_advance_ws(git_parse_ctx *ctx); int git_parse_advance_nl(git_parse_ctx *ctx); int git_parse_advance_digit(int64_t *out, git_parse_ctx *ctx, int base); +int git_parse_advance_oid(git_oid *out, git_parse_ctx *ctx); enum GIT_PARSE_PEEK_FLAGS { GIT_PARSE_PEEK_SKIP_WHITESPACE = (1 << 0) diff --git a/src/patch.c b/src/patch.c index d19e6833e..82181bb3d 100644 --- a/src/patch.c +++ b/src/patch.c @@ -79,7 +79,7 @@ size_t git_patch_size( git_buf file_header = GIT_BUF_INIT; if (git_diff_delta__format_file_header( - &file_header, patch->delta, NULL, NULL, 0) < 0) + &file_header, patch->delta, NULL, NULL, 0, true) < 0) git_error_clear(); else out += git_buf_len(&file_header); diff --git a/src/patch_generate.c b/src/patch_generate.c index 80033ee53..18256d076 100644 --- a/src/patch_generate.c +++ b/src/patch_generate.c @@ -15,7 +15,7 @@ #include "diff_xdiff.h" #include "delta.h" #include "zstream.h" -#include "fileops.h" +#include "futils.h" static void diff_output_init( git_patch_generated_output *, const git_diff_options *, git_diff_file_cb, @@ -836,7 +836,7 @@ static int patch_generated_line_cb( { git_patch_generated *patch = payload; git_patch_hunk *hunk; - git_diff_line *line; + git_diff_line *line; GIT_UNUSED(delta); GIT_UNUSED(hunk_); diff --git a/src/patch_parse.c b/src/patch_parse.c index d7311157f..9d089ad83 100644 --- a/src/patch_parse.c +++ b/src/patch_parse.c @@ -33,7 +33,19 @@ typedef struct { char *old_prefix, *new_prefix; } git_patch_parsed; -static int header_path_len(git_patch_parse_ctx *ctx) +static int git_parse_err(const char *fmt, ...) GIT_FORMAT_PRINTF(1, 2); +static int git_parse_err(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + git_error_vset(GIT_ERROR_PATCH, fmt, ap); + va_end(ap); + + return -1; +} + +static size_t header_path_len(git_patch_parse_ctx *ctx) { bool inquote = 0; bool quoted = git_parse_ctx_contains_s(&ctx->parse_ctx, "\""); @@ -58,31 +70,36 @@ static int parse_header_path_buf(git_buf *path, git_patch_parse_ctx *ctx, size_t int error; if ((error = git_buf_put(path, ctx->parse_ctx.line, path_len)) < 0) - goto done; + return error; git_parse_advance_chars(&ctx->parse_ctx, path_len); git_buf_rtrim(path); - if (path->size > 0 && path->ptr[0] == '"') - error = git_buf_unquote(path); - - if (error < 0) - goto done; + if (path->size > 0 && path->ptr[0] == '"' && + (error = git_buf_unquote(path)) < 0) + return error; git_path_squash_slashes(path); -done: - return error; + if (!path->size) + return git_parse_err("patch contains empty path at line %"PRIuZ, + ctx->parse_ctx.line_num); + + return 0; } static int parse_header_path(char **out, git_patch_parse_ctx *ctx) { git_buf path = GIT_BUF_INIT; - int error = parse_header_path_buf(&path, ctx, header_path_len(ctx)); + int error; + if ((error = parse_header_path_buf(&path, ctx, header_path_len(ctx))) < 0) + goto out; *out = git_buf_detach(&path); +out: + git_buf_dispose(&path); return error; } @@ -92,6 +109,12 @@ static int parse_header_git_oldpath( git_buf old_path = GIT_BUF_INIT; int error; + if (patch->old_path) { + error = git_parse_err("patch contains duplicate old path at line %"PRIuZ, + ctx->parse_ctx.line_num); + goto out; + } + if ((error = parse_header_path_buf(&old_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) goto out; @@ -108,9 +131,14 @@ static int parse_header_git_newpath( git_buf new_path = GIT_BUF_INIT; int error; + if (patch->new_path) { + error = git_parse_err("patch contains duplicate new path at line %"PRIuZ, + ctx->parse_ctx.line_num); + goto out; + } + if ((error = parse_header_path_buf(&new_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) goto out; - patch->new_path = git_buf_detach(&new_path); out: @@ -203,9 +231,9 @@ static int parse_header_git_deletedfilemode( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { - git__free((char *)patch->base.delta->old_file.path); + git__free((char *)patch->base.delta->new_file.path); - patch->base.delta->old_file.path = NULL; + patch->base.delta->new_file.path = NULL; patch->base.delta->status = GIT_DELTA_DELETED; patch->base.delta->nfiles = 1; @@ -216,9 +244,9 @@ static int parse_header_git_newfilemode( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { - git__free((char *)patch->base.delta->new_file.path); + git__free((char *)patch->base.delta->old_file.path); - patch->base.delta->new_file.path = NULL; + patch->base.delta->old_file.path = NULL; patch->base.delta->status = GIT_DELTA_ADDED; patch->base.delta->nfiles = 1; @@ -377,6 +405,7 @@ static const parse_header_transition transitions[] = { { "index " , STATE_DIFF, STATE_INDEX, parse_header_git_index }, { "index " , STATE_END, STATE_INDEX, parse_header_git_index }, + { "--- " , STATE_DIFF, STATE_PATH, parse_header_git_oldpath }, { "--- " , STATE_INDEX, STATE_PATH, parse_header_git_oldpath }, { "+++ " , STATE_PATH, STATE_END, parse_header_git_newpath }, { "GIT binary patch" , STATE_INDEX, STATE_END, NULL }, @@ -394,6 +423,7 @@ static const parse_header_transition transitions[] = { /* Next patch */ { "diff --git " , STATE_END, 0, NULL }, { "@@ -" , STATE_END, 0, NULL }, + { "-- " , STATE_INDEX, 0, NULL }, { "-- " , STATE_END, 0, NULL }, }; @@ -461,7 +491,7 @@ done: static int parse_int(int *out, git_patch_parse_ctx *ctx) { - git_off_t num; + int64_t num; if (git_parse_advance_digit(&num, &ctx->parse_ctx, 10) < 0 || !git__is_int(num)) return -1; @@ -524,6 +554,14 @@ fail: return -1; } +static int eof_for_origin(int origin) { + if (origin == GIT_DIFF_LINE_ADDITION) + return GIT_DIFF_LINE_ADD_EOFNL; + if (origin == GIT_DIFF_LINE_DELETION) + return GIT_DIFF_LINE_DEL_EOFNL; + return GIT_DIFF_LINE_CONTEXT_EOFNL; +} + static int parse_hunk_body( git_patch_parsed *patch, git_patch_hunk *hunk, @@ -534,6 +572,7 @@ static int parse_hunk_body( int oldlines = hunk->hunk.old_lines; int newlines = hunk->hunk.new_lines; + int last_origin = 0; for (; ctx->parse_ctx.remain_len > 1 && @@ -541,11 +580,17 @@ static int parse_hunk_body( !git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -"); git_parse_advance_line(&ctx->parse_ctx)) { + int old_lineno, new_lineno, origin, prefix = 1; char c; - int origin; - int prefix = 1; - int old_lineno = hunk->hunk.old_start + (hunk->hunk.old_lines - oldlines); - int new_lineno = hunk->hunk.new_start + (hunk->hunk.new_lines - newlines); + + if (git__add_int_overflow(&old_lineno, hunk->hunk.old_start, hunk->hunk.old_lines) || + git__sub_int_overflow(&old_lineno, old_lineno, oldlines) || + git__add_int_overflow(&new_lineno, hunk->hunk.new_start, hunk->hunk.new_lines) || + git__sub_int_overflow(&new_lineno, new_lineno, newlines)) { + error = git_parse_err("unrepresentable line count at line %"PRIuZ, + ctx->parse_ctx.line_num); + goto done; + } if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') { error = git_parse_err("invalid patch instruction at line %"PRIuZ, @@ -578,6 +623,21 @@ static int parse_hunk_body( old_lineno = -1; break; + case '\\': + /* + * If there are no oldlines left, then this is probably + * the "\ No newline at end of file" marker. Do not + * verify its format, as it may be localized. + */ + if (!oldlines) { + prefix = 0; + origin = eof_for_origin(last_origin); + old_lineno = -1; + new_lineno = -1; + break; + } + /* fall through */ + default: error = git_parse_err("invalid patch hunk at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; @@ -588,8 +648,9 @@ static int parse_hunk_body( memset(line, 0x0, sizeof(git_diff_line)); - line->content = ctx->parse_ctx.line + prefix; line->content_len = ctx->parse_ctx.line_len - prefix; + line->content = git__strndup(ctx->parse_ctx.line + prefix, line->content_len); + GIT_ERROR_CHECK_ALLOC(line->content); line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len; line->origin = origin; line->num_lines = 1; @@ -597,6 +658,8 @@ static int parse_hunk_body( line->new_lineno = new_lineno; hunk->line_count++; + + last_origin = origin; } if (oldlines || newlines) { @@ -606,7 +669,8 @@ static int parse_hunk_body( goto done; } - /* Handle "\ No newline at end of file". Only expect the leading + /* + * Handle "\ No newline at end of file". Only expect the leading * backslash, though, because the rest of the string could be * localized. Because `diff` optimizes for the case where you * want to apply the patch by hand. @@ -617,11 +681,25 @@ static int parse_hunk_body( line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1); if (line->content_len < 1) { - error = git_parse_err("cannot trim trailing newline of empty line"); + error = git_parse_err("last line has no trailing newline"); goto done; } - line->content_len--; + line = git_array_alloc(patch->base.lines); + GIT_ERROR_CHECK_ALLOC(line); + + memset(line, 0x0, sizeof(git_diff_line)); + + line->content_len = ctx->parse_ctx.line_len; + line->content = git__strndup(ctx->parse_ctx.line, line->content_len); + GIT_ERROR_CHECK_ALLOC(line->content); + line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len; + line->origin = eof_for_origin(last_origin); + line->num_lines = 1; + line->old_lineno = -1; + line->new_lineno = -1; + + hunk->line_count++; git_parse_advance_line(&ctx->parse_ctx); } @@ -687,7 +765,7 @@ static int parse_patch_binary_side( { git_diff_binary_t type = GIT_DIFF_BINARY_NONE; git_buf base85 = GIT_BUF_INIT, decoded = GIT_BUF_INIT; - git_off_t len; + int64_t len; int error = 0; if (git_parse_ctx_contains_s(&ctx->parse_ctx, "literal ")) { @@ -730,7 +808,7 @@ static int parse_patch_binary_side( encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5; - if (encoded_len > ctx->parse_ctx.line_len - 1) { + if (!encoded_len || !ctx->parse_ctx.line_len || encoded_len > ctx->parse_ctx.line_len - 1) { error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); goto done; } @@ -800,12 +878,23 @@ static int parse_patch_binary_nodata( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { + const char *old = patch->old_path ? patch->old_path : patch->header_old_path; + const char *new = patch->new_path ? patch->new_path : patch->header_new_path; + + if (!old || !new) + return git_parse_err("corrupt binary data without paths at line %"PRIuZ, ctx->parse_ctx.line_num); + + if (patch->base.delta->status == GIT_DELTA_ADDED) + old = "/dev/null"; + else if (patch->base.delta->status == GIT_DELTA_DELETED) + new = "/dev/null"; + if (git_parse_advance_expected_str(&ctx->parse_ctx, "Binary files ") < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, patch->header_old_path) < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, " and ") < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, patch->header_new_path) < 0 || - git_parse_advance_expected_str(&ctx->parse_ctx, " differ") < 0 || - git_parse_advance_nl(&ctx->parse_ctx) < 0) + git_parse_advance_expected_str(&ctx->parse_ctx, old) < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, " and ") < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, new) < 0 || + git_parse_advance_expected_str(&ctx->parse_ctx, " differ") < 0 || + git_parse_advance_nl(&ctx->parse_ctx) < 0) return git_parse_err("corrupt git binary header at line %"PRIuZ, ctx->parse_ctx.line_num); patch->base.binary.contains_data = 0; @@ -936,13 +1025,17 @@ static int check_filenames(git_patch_parsed *patch) /* Prefer the rename filenames as they are unambiguous and unprefixed */ if (patch->rename_old_path) patch->base.delta->old_file.path = patch->rename_old_path; - else + else if (prefixed_old) patch->base.delta->old_file.path = prefixed_old + old_prefixlen; + else + patch->base.delta->old_file.path = NULL; if (patch->rename_new_path) patch->base.delta->new_file.path = patch->rename_new_path; - else + else if (prefixed_new) patch->base.delta->new_file.path = prefixed_new + new_prefixlen; + else + patch->base.delta->new_file.path = NULL; if (!patch->base.delta->old_file.path && !patch->base.delta->new_file.path) @@ -1038,6 +1131,8 @@ int git_patch_parsed_from_diff(git_patch **out, git_diff *d, size_t idx) static void patch_parsed__free(git_patch *p) { git_patch_parsed *patch = (git_patch_parsed *)p; + git_diff_line *line; + size_t i; if (!patch) return; @@ -1047,6 +1142,8 @@ static void patch_parsed__free(git_patch *p) git__free((char *)patch->base.binary.old_file.data); git__free((char *)patch->base.binary.new_file.data); git_array_clear(patch->base.hunks); + git_array_foreach(patch->base.lines, i, line) + git__free((char *) line->content); git_array_clear(patch->base.lines); git__free(patch->base.delta); diff --git a/src/path.c b/src/path.c index fa94f0058..625b95c0d 100644 --- a/src/path.c +++ b/src/path.c @@ -14,7 +14,7 @@ #include "win32/w32_buffer.h" #include "win32/w32_util.h" #include "win32/version.h" -#include +#include #else #include #endif @@ -183,7 +183,13 @@ int git_path_dirname_r(git_buf *buffer, const char *path) while (endp > path && *endp == '/') endp--; - if ((len = win32_prefix_length(path, endp - path + 1)) > 0) { + if (endp - path + 1 > INT_MAX) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + len = -1; + goto Exit; + } + + if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { is_prefix = 1; goto Exit; } @@ -203,7 +209,13 @@ int git_path_dirname_r(git_buf *buffer, const char *path) endp--; } while (endp > path && *endp == '/'); - if ((len = win32_prefix_length(path, endp - path + 1)) > 0) { + if (endp - path + 1 > INT_MAX) { + git_error_set(GIT_ERROR_INVALID, "path too long"); + len = -1; + goto Exit; + } + + if ((len = win32_prefix_length(path, (int)(endp - path + 1))) > 0) { is_prefix = 1; goto Exit; } @@ -299,9 +311,12 @@ int git_path_root(const char *path) while (path[offset] && path[offset] != '/' && path[offset] != '\\') offset++; } + + if (path[offset] == '\\') + return offset; #endif - if (path[offset] == '/' || path[offset] == '\\') + if (path[offset] == '/') return offset; return -1; /* Not a real error - signals that path is not rooted */ @@ -1849,12 +1864,12 @@ GIT_INLINE(unsigned int) dotgit_flags( #endif if (repo && !protectHFS) - error = git_repository__cvar(&protectHFS, repo, GIT_CVAR_PROTECTHFS); + error = git_repository__configmap_lookup(&protectHFS, repo, GIT_CONFIGMAP_PROTECTHFS); if (!error && protectHFS) flags |= GIT_PATH_REJECT_DOT_GIT_HFS; if (repo) - error = git_repository__cvar(&protectNTFS, repo, GIT_CVAR_PROTECTNTFS); + error = git_repository__configmap_lookup(&protectNTFS, repo, GIT_CONFIGMAP_PROTECTNTFS); if (!error && protectNTFS) flags |= GIT_PATH_REJECT_DOT_GIT_NTFS; @@ -1942,6 +1957,28 @@ extern int git_path_is_gitfile(const char *path, size_t pathlen, git_path_gitfil } } +bool git_path_supports_symlinks(const char *dir) +{ + git_buf path = GIT_BUF_INIT; + bool supported = false; + struct stat st; + int fd; + + if ((fd = git_futils_mktmp(&path, dir, 0666)) < 0 || + p_close(fd) < 0 || + p_unlink(path.ptr) < 0 || + p_symlink("testing", path.ptr) < 0 || + p_lstat(path.ptr, &st) < 0) + goto done; + + supported = (S_ISLNK(st.st_mode) != 0); +done: + if (path.size) + (void)p_unlink(path.ptr); + git_buf_dispose(&path); + return supported; +} + int git_path_validate_system_file_ownership(const char *path) { #ifndef GIT_WIN32 diff --git a/src/path.h b/src/path.h index 5fac695e9..ed6b93574 100644 --- a/src/path.h +++ b/src/path.h @@ -647,6 +647,8 @@ extern bool git_path_isvalid( */ int git_path_normalize_slashes(git_buf *out, const char *path); +bool git_path_supports_symlinks(const char *dir); + /** * Validate a system file's ownership * diff --git a/src/pathspec.c b/src/pathspec.c index 07795f256..19ea9eb19 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -16,6 +16,7 @@ #include "index.h" #include "bitvec.h" #include "diff.h" +#include "wildmatch.h" /* what is the common non-wildcard prefix for all items in the pathspec */ char *git_pathspec_prefix(const git_strarray *pathspec) @@ -84,8 +85,7 @@ int git_pathspec__vinit( if (!match) return -1; - match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | - GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_NOLEADINGDIR; + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern); if (ret == GIT_ENOTFOUND) { @@ -110,7 +110,7 @@ void git_pathspec__vfree(git_vector *vspec) } struct pathspec_match_context { - int fnmatch_flags; + int wildmatch_flags; int (*strcomp)(const char *, const char *); int (*strncomp)(const char *, const char *, size_t); }; @@ -121,11 +121,11 @@ static void pathspec_match_context_init( bool casefold) { if (disable_fnmatch) - ctxt->fnmatch_flags = -1; + ctxt->wildmatch_flags = -1; else if (casefold) - ctxt->fnmatch_flags = FNM_CASEFOLD; + ctxt->wildmatch_flags = WM_CASEFOLD; else - ctxt->fnmatch_flags = 0; + ctxt->wildmatch_flags = 0; if (casefold) { ctxt->strcomp = git__strcasecmp; @@ -141,16 +141,16 @@ static int pathspec_match_one( struct pathspec_match_context *ctxt, const char *path) { - int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH; + int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : WM_NOMATCH; - if (result == FNM_NOMATCH) - result = ctxt->strcomp(match->pattern, path) ? FNM_NOMATCH : 0; + if (result == WM_NOMATCH) + result = ctxt->strcomp(match->pattern, path) ? WM_NOMATCH : 0; - if (ctxt->fnmatch_flags >= 0 && result == FNM_NOMATCH) - result = p_fnmatch(match->pattern, path, ctxt->fnmatch_flags); + if (ctxt->wildmatch_flags >= 0 && result == WM_NOMATCH) + result = wildmatch(match->pattern, path, ctxt->wildmatch_flags); /* if we didn't match, look for exact dirname prefix match */ - if (result == FNM_NOMATCH && + if (result == WM_NOMATCH && (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && ctxt->strncomp(path, match->pattern, match->length) == 0 && path[match->length] == '/') @@ -159,7 +159,7 @@ static int pathspec_match_one( /* if we didn't match and this is a negative match, check for exact * match of filename with leading '!' */ - if (result == FNM_NOMATCH && + if (result == WM_NOMATCH && (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 && *path == '!' && ctxt->strncomp(path + 1, match->pattern, match->length) == 0 && @@ -422,7 +422,7 @@ static int pathspec_match_from_iterator( if ((error = git_iterator_reset_range(iter, ps->prefix, ps->prefix)) < 0) goto done; - if (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR && + if (git_iterator_type(iter) == GIT_ITERATOR_WORKDIR && (error = git_repository_index__weakptr( &index, git_iterator_owner(iter))) < 0) goto done; diff --git a/src/pool.c b/src/pool.c index c0efe9c9d..b3bc8d489 100644 --- a/src/pool.c +++ b/src/pool.c @@ -14,30 +14,30 @@ struct git_pool_page { git_pool_page *next; - uint32_t size; - uint32_t avail; + size_t size; + size_t avail; GIT_ALIGN(char data[GIT_FLEX_ARRAY], 8); }; -static void *pool_alloc_page(git_pool *pool, uint32_t size); +static void *pool_alloc_page(git_pool *pool, size_t size); -uint32_t git_pool__system_page_size(void) +size_t git_pool__system_page_size(void) { - static uint32_t size = 0; + static size_t size = 0; if (!size) { size_t page_size; if (git__page_size(&page_size) < 0) page_size = 4096; /* allow space for malloc overhead */ - size = page_size - (2 * sizeof(void *)) - sizeof(git_pool_page); + size = (page_size - (2 * sizeof(void *)) - sizeof(git_pool_page)); } return size; } #ifndef GIT_DEBUG_POOL -void git_pool_init(git_pool *pool, uint32_t item_size) +void git_pool_init(git_pool *pool, size_t item_size) { assert(pool); assert(item_size >= 1); @@ -59,10 +59,10 @@ void git_pool_clear(git_pool *pool) pool->pages = NULL; } -static void *pool_alloc_page(git_pool *pool, uint32_t size) +static void *pool_alloc_page(git_pool *pool, size_t size) { git_pool_page *page; - const uint32_t new_page_size = (size <= pool->page_size) ? pool->page_size : size; + const size_t new_page_size = (size <= pool->page_size) ? pool->page_size : size; size_t alloc_size; if (GIT_ADD_SIZET_OVERFLOW(&alloc_size, new_page_size, sizeof(git_pool_page)) || @@ -78,7 +78,7 @@ static void *pool_alloc_page(git_pool *pool, uint32_t size) return page->data; } -static void *pool_alloc(git_pool *pool, uint32_t size) +static void *pool_alloc(git_pool *pool, size_t size) { git_pool_page *page = pool->pages; void *ptr = NULL; @@ -125,7 +125,7 @@ static int git_pool__ptr_cmp(const void * a, const void * b) } } -void git_pool_init(git_pool *pool, uint32_t item_size) +void git_pool_init(git_pool *pool, size_t item_size) { assert(pool); assert(item_size >= 1); @@ -141,7 +141,7 @@ void git_pool_clear(git_pool *pool) git_vector_free_deep(&pool->allocations); } -static void *pool_alloc(git_pool *pool, uint32_t size) { +static void *pool_alloc(git_pool *pool, size_t size) { void *ptr = NULL; if((ptr = git__malloc(size)) == NULL) { return NULL; @@ -169,26 +169,26 @@ void git_pool_swap(git_pool *a, git_pool *b) memcpy(b, &temp, sizeof(temp)); } -static uint32_t alloc_size(git_pool *pool, uint32_t count) +static size_t alloc_size(git_pool *pool, size_t count) { - const uint32_t align = sizeof(void *) - 1; + const size_t align = sizeof(void *) - 1; if (pool->item_size > 1) { - const uint32_t item_size = (pool->item_size + align) & ~align; + const size_t item_size = (pool->item_size + align) & ~align; return item_size * count; } return (count + align) & ~align; } -void *git_pool_malloc(git_pool *pool, uint32_t items) +void *git_pool_malloc(git_pool *pool, size_t items) { return pool_alloc(pool, alloc_size(pool, items)); } -void *git_pool_mallocz(git_pool *pool, uint32_t items) +void *git_pool_mallocz(git_pool *pool, size_t items) { - const uint32_t size = alloc_size(pool, items); + const size_t size = alloc_size(pool, items); void *ptr = pool_alloc(pool, size); if (ptr) memset(ptr, 0x0, size); @@ -201,10 +201,10 @@ char *git_pool_strndup(git_pool *pool, const char *str, size_t n) assert(pool && str && pool->item_size == sizeof(char)); - if ((uint32_t)(n + 1) < n) + if (n == SIZE_MAX) return NULL; - if ((ptr = git_pool_malloc(pool, (uint32_t)(n + 1))) != NULL) { + if ((ptr = git_pool_malloc(pool, (n + 1))) != NULL) { memcpy(ptr, str, n); ptr[n] = '\0'; } @@ -226,14 +226,18 @@ char *git_pool_strdup_safe(git_pool *pool, const char *str) char *git_pool_strcat(git_pool *pool, const char *a, const char *b) { void *ptr; - size_t len_a, len_b; + size_t len_a, len_b, total; assert(pool && pool->item_size == sizeof(char)); len_a = a ? strlen(a) : 0; len_b = b ? strlen(b) : 0; - if ((ptr = git_pool_malloc(pool, (uint32_t)(len_a + len_b + 1))) != NULL) { + if (GIT_ADD_SIZET_OVERFLOW(&total, len_a, len_b) || + GIT_ADD_SIZET_OVERFLOW(&total, total, 1)) + return NULL; + + if ((ptr = git_pool_malloc(pool, total)) != NULL) { if (len_a) memcpy(ptr, a, len_a); if (len_b) diff --git a/src/pool.h b/src/pool.h index 92ddf994a..23f68990f 100644 --- a/src/pool.h +++ b/src/pool.h @@ -32,8 +32,8 @@ typedef struct git_pool_page git_pool_page; */ typedef struct { git_pool_page *pages; /* allocated pages */ - uint32_t item_size; /* size of single alloc unit in bytes */ - uint32_t page_size; /* size of page in bytes */ + size_t item_size; /* size of single alloc unit in bytes */ + size_t page_size; /* size of page in bytes */ } git_pool; #define GIT_POOL_INIT { NULL, 0, 0 } @@ -57,8 +57,8 @@ typedef struct { */ typedef struct { git_vector allocations; - uint32_t item_size; - uint32_t page_size; + size_t item_size; + size_t page_size; } git_pool; #define GIT_POOL_INIT { GIT_VECTOR_INIT, 0, 0 } @@ -81,7 +81,7 @@ typedef struct { * Of course, you can use this in other ways, but those are the * two most common patterns. */ -extern void git_pool_init(git_pool *pool, uint32_t item_size); +extern void git_pool_init(git_pool *pool, size_t item_size); /** * Free all items in pool @@ -96,8 +96,8 @@ extern void git_pool_swap(git_pool *a, git_pool *b); /** * Allocate space for one or more items from a pool. */ -extern void *git_pool_malloc(git_pool *pool, uint32_t items); -extern void *git_pool_mallocz(git_pool *pool, uint32_t items); +extern void *git_pool_malloc(git_pool *pool, size_t items); +extern void *git_pool_mallocz(git_pool *pool, size_t items); /** * Allocate space and duplicate string data into it. diff --git a/src/posix.c b/src/posix.c index bffe02e05..fbaa7c3ca 100644 --- a/src/posix.c +++ b/src/posix.c @@ -28,11 +28,11 @@ int p_getaddrinfo( GIT_UNUSED(hints); - if ((ainfo = malloc(sizeof(struct addrinfo))) == NULL) + if ((ainfo = git__malloc(sizeof(struct addrinfo))) == NULL) return -1; if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) { - free(ainfo); + git__free(ainfo); return -2; } @@ -65,7 +65,7 @@ int p_getaddrinfo( ai = ainfo; for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) { - if (!(ai->ai_next = malloc(sizeof(struct addrinfo)))) { + if (!(ai->ai_next = git__malloc(sizeof(struct addrinfo)))) { p_freeaddrinfo(ainfo); return -1; } @@ -89,7 +89,7 @@ void p_freeaddrinfo(struct addrinfo *info) while(p != NULL) { next = p->ai_next; - free(p); + git__free(p); p = next; } } @@ -235,7 +235,7 @@ int git__mmap_alignment(size_t *alignment) } -int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) { GIT_MMAP_VALIDATE(out, len, prot, flags); @@ -247,7 +247,7 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs return -1; } - out->data = malloc(len); + out->data = git__malloc(len); GIT_ERROR_CHECK_ALLOC(out->data); if (!git__is_ssizet(len) || @@ -264,7 +264,7 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs int p_munmap(git_map *map) { assert(map != NULL); - free(map->data); + git__free(map->data); return 0; } diff --git a/src/posix.h b/src/posix.h index 2934f2479..eef667762 100644 --- a/src/posix.h +++ b/src/posix.h @@ -11,7 +11,6 @@ #include #include -#include "fnmatch.h" /* stat: file mode type testing macros */ #ifndef S_IFGITLINK @@ -90,6 +89,18 @@ #define EAFNOSUPPORT (INT_MAX-1) #endif +/* Provide a 64-bit size for offsets. */ + +#if defined(_MSC_VER) +typedef __int64 off64_t; +#elif defined(__HAIKU__) +typedef __haiku_std_int64 off64_t; +#elif defined(__APPLE__) +typedef __int64_t off64_t; +#else +typedef int64_t off64_t; +#endif + typedef int git_file; /** diff --git a/src/proxy.c b/src/proxy.c index d02558623..367c4b1f7 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -9,17 +9,22 @@ #include "git2/proxy.h" -int git_proxy_init_options(git_proxy_options *opts, unsigned int version) +int git_proxy_options_init(git_proxy_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_proxy_options, GIT_PROXY_OPTIONS_INIT); return 0; } +int git_proxy_init_options(git_proxy_options *opts, unsigned int version) +{ + return git_proxy_options_init(opts, version); +} + int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src) { if (!src) { - git_proxy_init_options(tgt, GIT_PROXY_OPTIONS_VERSION); + git_proxy_options_init(tgt, GIT_PROXY_OPTIONS_VERSION); return 0; } diff --git a/src/push.c b/src/push.c index 9d09e18bb..34867c2e4 100644 --- a/src/push.c +++ b/src/push.c @@ -196,7 +196,7 @@ int git_push_update_tips(git_push *push, const git_remote_callbacks *callbacks) continue; /* Update the remote ref */ - if (git_oid_iszero(&push_spec->loid)) { + if (git_oid_is_zero(&push_spec->loid)) { error = git_reference_lookup(&remote_ref, push->remote->repo, git_buf_cstr(&remote_ref_name)); if (error >= 0) { @@ -281,7 +281,7 @@ static int queue_objects(git_push *push) git_object_t type; size_t size; - if (git_oid_iszero(&spec->loid)) + if (git_oid_is_zero(&spec->loid)) /* * Delete reference on remote side; * nothing to do here. @@ -319,7 +319,7 @@ static int queue_objects(git_push *push) if (!spec->refspec.force) { git_oid base; - if (git_oid_iszero(&spec->roid)) + if (git_oid_is_zero(&spec->roid)) continue; if (!git_odb_exists(push->repo->_odb, &spec->roid)) { @@ -346,11 +346,12 @@ static int queue_objects(git_push *push) } git_vector_foreach(&push->remote->refs, i, head) { - if (git_oid_iszero(&head->oid)) + if (git_oid_is_zero(&head->oid)) continue; - /* TODO */ - git_revwalk_hide(rw, &head->oid); + if ((error = git_revwalk_hide(rw, &head->oid)) < 0 && + error != GIT_ENOTFOUND && error != GIT_EINVALIDSPEC && error != GIT_EPEEL) + goto on_error; } error = git_packbuilder_insert_walk(push->pb, rw); @@ -547,9 +548,14 @@ void git_push_free(git_push *push) git__free(push); } -int git_push_init_options(git_push_options *opts, unsigned int version) +int git_push_options_init(git_push_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_push_options, GIT_PUSH_OPTIONS_INIT); return 0; } + +int git_push_init_options(git_push_options *opts, unsigned int version) +{ + return git_push_options_init(opts, version); +} diff --git a/src/reader.c b/src/reader.c index 1a4844698..90f700a00 100644 --- a/src/reader.c +++ b/src/reader.c @@ -7,7 +7,7 @@ #include "reader.h" -#include "fileops.h" +#include "futils.h" #include "blob.h" #include "git2/tree.h" @@ -32,7 +32,7 @@ static int tree_reader_read( tree_reader *reader = (tree_reader *)_reader; git_tree_entry *tree_entry = NULL; git_blob *blob = NULL; - git_off_t blobsize; + git_object_size_t blobsize; int error; if ((error = git_tree_entry_bypath(&tree_entry, reader->tree, filename)) < 0 || diff --git a/src/rebase.c b/src/rebase.c index 45460367e..0a38807a7 100644 --- a/src/rebase.c +++ b/src/rebase.c @@ -49,18 +49,18 @@ #define REBASE_FILE_MODE 0666 typedef enum { - GIT_REBASE_TYPE_NONE = 0, - GIT_REBASE_TYPE_APPLY = 1, - GIT_REBASE_TYPE_MERGE = 2, - GIT_REBASE_TYPE_INTERACTIVE = 3, -} git_rebase_type_t; + GIT_REBASE_NONE = 0, + GIT_REBASE_APPLY = 1, + GIT_REBASE_MERGE = 2, + GIT_REBASE_INTERACTIVE = 3, +} git_rebase_t; struct git_rebase { git_repository *repo; git_rebase_options options; - git_rebase_type_t type; + git_rebase_t type; char *state_path; int head_detached : 1, @@ -86,18 +86,18 @@ struct git_rebase { #define GIT_REBASE_STATE_INIT {0} static int rebase_state_type( - git_rebase_type_t *type_out, + git_rebase_t *type_out, char **path_out, git_repository *repo) { git_buf path = GIT_BUF_INIT; - git_rebase_type_t type = GIT_REBASE_TYPE_NONE; + git_rebase_t type = GIT_REBASE_NONE; if (git_buf_joinpath(&path, repo->gitdir, REBASE_APPLY_DIR) < 0) return -1; if (git_path_isdir(git_buf_cstr(&path))) { - type = GIT_REBASE_TYPE_APPLY; + type = GIT_REBASE_APPLY; goto done; } @@ -106,14 +106,14 @@ static int rebase_state_type( return -1; if (git_path_isdir(git_buf_cstr(&path))) { - type = GIT_REBASE_TYPE_MERGE; + type = GIT_REBASE_MERGE; goto done; } done: *type_out = type; - if (type != GIT_REBASE_TYPE_NONE && path_out) + if (type != GIT_REBASE_NONE && path_out) *path_out = git_buf_detach(&path); git_buf_dispose(&path); @@ -268,7 +268,7 @@ static int rebase_alloc(git_rebase **out, const git_rebase_options *rebase_opts) if (rebase_opts) memcpy(&rebase->options, rebase_opts, sizeof(git_rebase_options)); else - git_rebase_init_options(&rebase->options, GIT_REBASE_OPTIONS_VERSION); + git_rebase_options_init(&rebase->options, GIT_REBASE_OPTIONS_VERSION); if (rebase_opts && rebase_opts->rewrite_notes_ref) { rebase->options.rewrite_notes_ref = git__strdup(rebase_opts->rewrite_notes_ref); @@ -298,7 +298,8 @@ int git_rebase_open( git_rebase *rebase; git_buf path = GIT_BUF_INIT, orig_head_name = GIT_BUF_INIT, orig_head_id = GIT_BUF_INIT, onto_id = GIT_BUF_INIT; - int state_path_len, error; + size_t state_path_len; + int error; assert(repo); @@ -313,7 +314,7 @@ int git_rebase_open( if ((error = rebase_state_type(&rebase->type, &rebase->state_path, repo)) < 0) goto done; - if (rebase->type == GIT_REBASE_TYPE_NONE) { + if (rebase->type == GIT_REBASE_NONE) { git_error_set(GIT_ERROR_REBASE, "there is no rebase in progress"); error = GIT_ENOTFOUND; goto done; @@ -369,14 +370,14 @@ int git_rebase_open( rebase->orig_head_name = git_buf_detach(&orig_head_name); switch (rebase->type) { - case GIT_REBASE_TYPE_INTERACTIVE: + case GIT_REBASE_INTERACTIVE: git_error_set(GIT_ERROR_REBASE, "interactive rebase is not supported"); error = -1; break; - case GIT_REBASE_TYPE_MERGE: + case GIT_REBASE_MERGE: error = rebase_open_merge(rebase); break; - case GIT_REBASE_TYPE_APPLY: + case GIT_REBASE_APPLY: git_error_set(GIT_ERROR_REBASE, "patch application rebase is not supported"); error = -1; break; @@ -493,22 +494,27 @@ static int rebase_setupfiles(git_rebase *rebase) return rebase_setupfiles_merge(rebase); } -int git_rebase_init_options(git_rebase_options *opts, unsigned int version) +int git_rebase_options_init(git_rebase_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_rebase_options, GIT_REBASE_OPTIONS_INIT); return 0; } +int git_rebase_init_options(git_rebase_options *opts, unsigned int version) +{ + return git_rebase_options_init(opts, version); +} + static int rebase_ensure_not_in_progress(git_repository *repo) { int error; - git_rebase_type_t type; + git_rebase_t type; if ((error = rebase_state_type(&type, NULL, repo)) < 0) return error; - if (type != GIT_REBASE_TYPE_NONE) { + if (type != GIT_REBASE_NONE) { git_error_set(GIT_ERROR_REBASE, "there is an existing rebase in progress"); return -1; } @@ -723,7 +729,7 @@ int git_rebase_init( rebase->repo = repo; rebase->inmemory = inmemory; - rebase->type = GIT_REBASE_TYPE_MERGE; + rebase->type = GIT_REBASE_MERGE; if ((error = rebase_init_operations(rebase, repo, branch, upstream, onto)) < 0) goto done; @@ -758,7 +764,7 @@ static void normalize_checkout_options_for_apply( if (!checkout_opts->ancestor_label) checkout_opts->ancestor_label = "ancestor"; - if (rebase->type == GIT_REBASE_TYPE_MERGE) { + if (rebase->type == GIT_REBASE_MERGE) { if (!checkout_opts->our_label) checkout_opts->our_label = rebase->onto_name; @@ -911,7 +917,7 @@ int git_rebase_next( if (rebase->inmemory) error = rebase_next_inmemory(out, rebase); - else if (rebase->type == GIT_REBASE_TYPE_MERGE) + else if (rebase->type == GIT_REBASE_MERGE) error = rebase_next_merge(out, rebase); else abort(); @@ -945,6 +951,10 @@ static int rebase_commit__create( git_commit *current_commit = NULL, *commit = NULL; git_tree *parent_tree = NULL, *tree = NULL; git_oid tree_id, commit_id; + git_buf commit_content = GIT_BUF_INIT, commit_signature = GIT_BUF_INIT, + signature_field = GIT_BUF_INIT; + const char *signature_field_string = NULL, + *commit_signature_string = NULL; int error; operation = git_array_get(rebase->operations, rebase->current); @@ -975,10 +985,40 @@ static int rebase_commit__create( message = git_commit_message(current_commit); } - if ((error = git_commit_create(&commit_id, rebase->repo, NULL, author, - committer, message_encoding, message, tree, 1, - (const git_commit **)&parent_commit)) < 0 || - (error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0) + if ((error = git_commit_create_buffer(&commit_content, rebase->repo, author, committer, + message_encoding, message, tree, 1, (const git_commit **)&parent_commit)) < 0) + goto done; + + if (rebase->options.signing_cb) { + git_error_clear(); + error = git_error_set_after_callback_function(rebase->options.signing_cb( + &commit_signature, &signature_field, git_buf_cstr(&commit_content), + rebase->options.payload), "commit signing_cb failed"); + if (error == GIT_PASSTHROUGH) { + git_buf_dispose(&commit_signature); + git_buf_dispose(&signature_field); + git_error_clear(); + error = GIT_OK; + } else if (error < 0) + goto done; + } + + if (git_buf_is_allocated(&commit_signature)) { + assert(git_buf_contains_nul(&commit_signature)); + commit_signature_string = git_buf_cstr(&commit_signature); + } + + if (git_buf_is_allocated(&signature_field)) { + assert(git_buf_contains_nul(&signature_field)); + signature_field_string = git_buf_cstr(&signature_field); + } + + if ((error = git_commit_create_with_signature(&commit_id, rebase->repo, + git_buf_cstr(&commit_content), commit_signature_string, + signature_field_string))) + goto done; + + if ((error = git_commit_lookup(&commit, rebase->repo, &commit_id)) < 0) goto done; *out = commit; @@ -987,6 +1027,9 @@ done: if (error < 0) git_commit_free(commit); + git_buf_dispose(&commit_signature); + git_buf_dispose(&signature_field); + git_buf_dispose(&commit_content); git_commit_free(current_commit); git_tree_free(parent_tree); git_tree_free(tree); @@ -1085,7 +1128,7 @@ int git_rebase_commit( if (rebase->inmemory) error = rebase_commit_inmemory( id, rebase, author, committer, message_encoding, message); - else if (rebase->type == GIT_REBASE_TYPE_MERGE) + else if (rebase->type == GIT_REBASE_MERGE) error = rebase_commit_merge( id, rebase, author, committer, message_encoding, message); else @@ -1327,6 +1370,22 @@ int git_rebase_finish( return error; } +const char *git_rebase_orig_head_name(git_rebase *rebase) { + return rebase->orig_head_name; +} + +const git_oid *git_rebase_orig_head_id(git_rebase *rebase) { + return &rebase->orig_head_id; +} + +const char *git_rebase_onto_name(git_rebase *rebase) { + return rebase->onto_name; +} + +const git_oid *git_rebase_onto_id(git_rebase *rebase) { + return &rebase->onto_id; +} + size_t git_rebase_operation_entrycount(git_rebase *rebase) { assert(rebase); diff --git a/src/refdb.c b/src/refdb.c index b466153a7..fbbf5193c 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -66,6 +66,18 @@ static void refdb_free_backend(git_refdb *db) int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend) { + GIT_ERROR_CHECK_VERSION(backend, GIT_REFDB_BACKEND_VERSION, "git_refdb_backend"); + + if (!backend->exists || !backend->lookup || !backend->iterator || + !backend->write || !backend->rename || !backend->del || + !backend->has_log || !backend->ensure_log || !backend->free || + !backend->reflog_read || !backend->reflog_write || + !backend->reflog_rename || !backend->reflog_delete || + (backend->lock && !backend->unlock)) { + git_error_set(GIT_ERROR_REFERENCE, "incomplete refdb backend implementation"); + return GIT_EINVALID; + } + refdb_free_backend(db); db->backend = backend; diff --git a/src/refdb_fs.c b/src/refdb_fs.c index d582cb8a2..1e53b3af5 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -10,14 +10,16 @@ #include "refs.h" #include "hash.h" #include "repository.h" -#include "fileops.h" +#include "futils.h" #include "filebuf.h" #include "pack.h" +#include "parse.h" #include "reflog.h" #include "refdb.h" #include "iterator.h" #include "sortedcache.h" #include "signature.h" +#include "wildmatch.h" #include #include @@ -327,21 +329,33 @@ static int refdb_fs_backend__exists( git_refdb_backend *_backend, const char *ref_name) { - refdb_fs_backend *backend = (refdb_fs_backend *)_backend; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); git_buf ref_path = GIT_BUF_INIT; int error; assert(backend); - if ((error = packed_reload(backend)) < 0 || - (error = git_buf_joinpath(&ref_path, backend->gitpath, ref_name)) < 0) - return error; + *exists = 0; - *exists = git_path_isfile(ref_path.ptr) || - (git_sortedcache_lookup(backend->refcache, ref_name) != NULL); + if ((error = git_buf_joinpath(&ref_path, backend->gitpath, ref_name)) < 0) + goto out; + if (git_path_isfile(ref_path.ptr)) { + *exists = 1; + goto out; + } + + if ((error = packed_reload(backend)) < 0) + goto out; + + if (git_sortedcache_lookup(backend->refcache, ref_name) != NULL) { + *exists = 1; + goto out; + } + +out: git_buf_dispose(&ref_path); - return 0; + return error; } static const char *loose_parse_symbolic(git_buf *file_content) @@ -457,7 +471,7 @@ static int refdb_fs_backend__lookup( git_refdb_backend *_backend, const char *ref_name) { - refdb_fs_backend *backend = (refdb_fs_backend *)_backend; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); int error; assert(backend); @@ -490,7 +504,7 @@ typedef struct { static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) { - refdb_fs_iter *iter = (refdb_fs_iter *) _iter; + refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); git_vector_free(&iter->loose); git_pool_clear(&iter->pool); @@ -552,7 +566,6 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) while (!error && !git_iterator_advance(&entry, fsit)) { const char *ref_name; - struct packref *ref; char *ref_dup; git_buf_truncate(&path, ref_prefix_len); @@ -560,15 +573,9 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) ref_name = git_buf_cstr(&path); if (git__suffixcmp(ref_name, ".lock") == 0 || - (iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0)) + (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0)) continue; - git_sortedcache_rlock(backend->refcache); - ref = git_sortedcache_lookup(backend->refcache, ref_name); - if (ref) - ref->flags |= PACKREF_SHADOWED; - git_sortedcache_runlock(backend->refcache); - ref_dup = git_pool_strdup(&iter->pool, ref_name); if (!ref_dup) error = -1; @@ -586,24 +593,24 @@ static int refdb_fs_backend__iterator_next( git_reference **out, git_reference_iterator *_iter) { int error = GIT_ITEROVER; - refdb_fs_iter *iter = (refdb_fs_iter *)_iter; - refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend; + refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); + refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent); struct packref *ref; while (iter->loose_pos < iter->loose.length) { const char *path = git_vector_get(&iter->loose, iter->loose_pos++); - if (loose_lookup(out, backend, path) == 0) + if (loose_lookup(out, backend, path) == 0) { + ref = git_sortedcache_lookup(iter->cache, path); + if (ref) + ref->flags |= PACKREF_SHADOWED; + return 0; + } git_error_clear(); } - if (!iter->cache) { - if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0) - return error; - } - error = GIT_ITEROVER; while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); @@ -612,7 +619,7 @@ static int refdb_fs_backend__iterator_next( if (ref->flags & PACKREF_SHADOWED) continue; - if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0) + if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) continue; *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel); @@ -627,14 +634,19 @@ static int refdb_fs_backend__iterator_next_name( const char **out, git_reference_iterator *_iter) { int error = GIT_ITEROVER; - refdb_fs_iter *iter = (refdb_fs_iter *)_iter; - refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend; + refdb_fs_iter *iter = GIT_CONTAINER_OF(_iter, refdb_fs_iter, parent); + refdb_fs_backend *backend = GIT_CONTAINER_OF(iter->parent.db->backend, refdb_fs_backend, parent); struct packref *ref; while (iter->loose_pos < iter->loose.length) { const char *path = git_vector_get(&iter->loose, iter->loose_pos++); + struct packref *ref; if (loose_lookup(NULL, backend, path) == 0) { + ref = git_sortedcache_lookup(iter->cache, path); + if (ref) + ref->flags |= PACKREF_SHADOWED; + *out = path; return 0; } @@ -642,11 +654,6 @@ static int refdb_fs_backend__iterator_next_name( git_error_clear(); } - if (!iter->cache) { - if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0) - return error; - } - error = GIT_ITEROVER; while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); @@ -655,7 +662,7 @@ static int refdb_fs_backend__iterator_next_name( if (ref->flags & PACKREF_SHADOWED) continue; - if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0) + if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) continue; *out = ref->name; @@ -669,40 +676,44 @@ static int refdb_fs_backend__iterator_next_name( static int refdb_fs_backend__iterator( git_reference_iterator **out, git_refdb_backend *_backend, const char *glob) { + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + refdb_fs_iter *iter = NULL; int error; - refdb_fs_iter *iter; - refdb_fs_backend *backend = (refdb_fs_backend *)_backend; assert(backend); - if ((error = packed_reload(backend)) < 0) - return error; - iter = git__calloc(1, sizeof(refdb_fs_iter)); GIT_ERROR_CHECK_ALLOC(iter); git_pool_init(&iter->pool, 1); - if (git_vector_init(&iter->loose, 8, NULL) < 0) - goto fail; + if ((error = git_vector_init(&iter->loose, 8, NULL)) < 0) + goto out; if (glob != NULL && - (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) - goto fail; + (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL) { + error = GIT_ERROR_NOMEMORY; + goto out; + } + + if ((error = iter_load_loose_paths(backend, iter)) < 0) + goto out; + + if ((error = packed_reload(backend)) < 0) + goto out; + + if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0) + goto out; iter->parent.next = refdb_fs_backend__iterator_next; iter->parent.next_name = refdb_fs_backend__iterator_next_name; iter->parent.free = refdb_fs_backend__iterator_free; - if (iter_load_loose_paths(backend, iter) < 0) - goto fail; - *out = (git_reference_iterator *)iter; - return 0; - -fail: - refdb_fs_backend__iterator_free((git_reference_iterator *)iter); - return -1; +out: + if (error) + refdb_fs_backend__iterator_free((git_reference_iterator *)iter); + return error; } static bool ref_is_available( @@ -794,7 +805,7 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char * if (git_buf_joinpath(&ref_path, basedir, name) < 0) return -1; - filebuf_flags = GIT_FILEBUF_FORCE; + filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS; if (backend->fsync) filebuf_flags |= GIT_FILEBUF_FSYNC; @@ -829,7 +840,7 @@ static int refdb_fs_backend__lock(void **out, git_refdb_backend *_backend, const { int error; git_filebuf *lock; - refdb_fs_backend *backend = (refdb_fs_backend *) _backend; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); lock = git__calloc(1, sizeof(git_filebuf)); GIT_ERROR_CHECK_ALLOC(lock); @@ -848,16 +859,17 @@ static int refdb_fs_backend__write_tail( const git_reference *ref, git_filebuf *file, int update_reflog, - const git_signature *who, - const char *message, const git_oid *old_id, - const char *old_target); + const char *old_target, + const git_signature *who, + const char *message); static int refdb_fs_backend__delete_tail( git_refdb_backend *_backend, git_filebuf *file, const char *ref_name, - const git_oid *old_id, const char *old_target); + const git_oid *old_id, + const char *old_target); static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, int success, int update_reflog, const git_reference *ref, const git_signature *sig, const char *message) @@ -868,7 +880,7 @@ static int refdb_fs_backend__unlock(git_refdb_backend *backend, void *payload, i if (success == 2) error = refdb_fs_backend__delete_tail(backend, lock, ref->name, NULL, NULL); else if (success) - error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, sig, message, NULL, NULL); + error = refdb_fs_backend__write_tail(backend, ref, lock, update_reflog, NULL, NULL, sig, message); else git_filebuf_cleanup(lock); @@ -1087,6 +1099,35 @@ fail: return error; } +static int packed_delete(refdb_fs_backend *backend, const char *ref_name) +{ + size_t pack_pos; + int error, found = 0; + + if ((error = packed_reload(backend)) < 0) + goto cleanup; + + if ((error = git_sortedcache_wlock(backend->refcache)) < 0) + goto cleanup; + + /* If a packed reference exists, remove it from the packfile and repack if necessary */ + error = git_sortedcache_lookup_index(&pack_pos, backend->refcache, ref_name); + if (error == 0) { + error = git_sortedcache_remove(backend->refcache, pack_pos); + found = 1; + } + if (error == GIT_ENOTFOUND) + error = 0; + + git_sortedcache_wunlock(backend->refcache); + + if (found) + error = packed_write(backend); + +cleanup: + return error; +} + static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *author, const char *message); static int has_reflog(git_repository *repo, const char *name); @@ -1094,7 +1135,7 @@ static int should_write_reflog(int *write, git_repository *repo, const char *nam { int error, logall; - error = git_repository__cvar(&logall, repo, GIT_CVAR_LOGALLREFUPDATES); + error = git_repository__configmap_lookup(&logall, repo, GIT_CONFIGMAP_LOGALLREFUPDATES); if (error < 0) return error; @@ -1239,7 +1280,7 @@ static int refdb_fs_backend__write( const git_oid *old_id, const char *old_target) { - refdb_fs_backend *backend = (refdb_fs_backend *)_backend; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); git_filebuf file = GIT_FILEBUF_INIT; int error = 0; @@ -1252,7 +1293,7 @@ static int refdb_fs_backend__write( if ((error = loose_lock(&file, backend, ref->name)) < 0) return error; - return refdb_fs_backend__write_tail(_backend, ref, &file, true, who, message, old_id, old_target); + return refdb_fs_backend__write_tail(_backend, ref, &file, true, old_id, old_target, who, message); } static int refdb_fs_backend__write_tail( @@ -1260,12 +1301,12 @@ static int refdb_fs_backend__write_tail( const git_reference *ref, git_filebuf *file, int update_reflog, - const git_signature *who, - const char *message, const git_oid *old_id, - const char *old_target) + const char *old_target, + const git_signature *who, + const char *message) { - refdb_fs_backend *backend = (refdb_fs_backend *)_backend; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); int error = 0, cmp = 0, should_write; const char *new_target = NULL; const git_oid *new_id = NULL; @@ -1313,10 +1354,10 @@ on_error: return error; } -static void refdb_fs_backend__try_delete_empty_ref_hierarchie( +static void refdb_fs_backend__prune_refs( refdb_fs_backend *backend, const char *ref_name, - bool reflog) + const char *prefix) { git_buf relative_path = GIT_BUF_INIT; git_buf base_path = GIT_BUF_INIT; @@ -1334,8 +1375,8 @@ static void refdb_fs_backend__try_delete_empty_ref_hierarchie( git_buf_truncate(&relative_path, commonlen); - if (reflog) { - if (git_buf_join3(&base_path, '/', backend->commonpath, GIT_REFLOG_DIR, git_buf_cstr(&relative_path)) < 0) + if (prefix) { + if (git_buf_join3(&base_path, '/', backend->commonpath, prefix, git_buf_cstr(&relative_path)) < 0) goto cleanup; } else { if (git_buf_joinpath(&base_path, backend->commonpath, git_buf_cstr(&relative_path)) < 0) @@ -1355,7 +1396,7 @@ static int refdb_fs_backend__delete( const char *ref_name, const git_oid *old_id, const char *old_target) { - refdb_fs_backend *backend = (refdb_fs_backend *)_backend; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); git_filebuf file = GIT_FILEBUF_INIT; int error = 0; @@ -1372,17 +1413,34 @@ static int refdb_fs_backend__delete( return refdb_fs_backend__delete_tail(_backend, &file, ref_name, old_id, old_target); } +static int loose_delete(refdb_fs_backend *backend, const char *ref_name) +{ + git_buf loose_path = GIT_BUF_INIT; + int error = 0; + + if (git_buf_joinpath(&loose_path, backend->commonpath, ref_name) < 0) + return -1; + + error = p_unlink(loose_path.ptr); + if (error < 0 && errno == ENOENT) + error = GIT_ENOTFOUND; + else if (error != 0) + error = -1; + + git_buf_dispose(&loose_path); + + return error; +} + static int refdb_fs_backend__delete_tail( git_refdb_backend *_backend, git_filebuf *file, const char *ref_name, const git_oid *old_id, const char *old_target) { - refdb_fs_backend *backend = (refdb_fs_backend *)_backend; - git_buf loose_path = GIT_BUF_INIT; - size_t pack_pos; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); int error = 0, cmp = 0; - bool loose_deleted = 0; + bool packed_deleted = 0; error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target); if (error < 0) @@ -1394,44 +1452,41 @@ static int refdb_fs_backend__delete_tail( goto cleanup; } - /* If a loose reference exists, remove it from the filesystem */ - if (git_buf_joinpath(&loose_path, backend->commonpath, ref_name) < 0) - return -1; - - - error = p_unlink(loose_path.ptr); - if (error < 0 && errno == ENOENT) - error = 0; - else if (error < 0) - goto cleanup; - else if (error == 0) - loose_deleted = 1; - - if ((error = packed_reload(backend)) < 0) + /* + * To ensure that an external observer will see either the current ref value + * (because the loose ref still exists), or a missing ref (after the packed-file is + * unlocked, there will be nothing left), we must ensure things happen in the + * following order: + * + * - the packed-ref file is locked and loaded, as well as a loose one, if it exists + * - we optimistically delete a packed ref, keeping track of whether it existed + * - we delete the loose ref, note that we have its .lock + * - the loose ref is "unlocked", then the packed-ref file is rewritten and unlocked + * - we should prune the path components if a loose ref was deleted + * + * Note that, because our packed backend doesn't expose its filesystem lock, + * we might not be able to guarantee that this is what actually happens (ie. + * as our current code never write packed-refs.lock, nothing stops observers + * from grabbing a "stale" value from there). + */ + if ((error = packed_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND) goto cleanup; - /* If a packed reference exists, remove it from the packfile and repack */ - if ((error = git_sortedcache_wlock(backend->refcache)) < 0) + if (error == 0) + packed_deleted = 1; + + if ((error = loose_delete(backend, ref_name)) < 0 && error != GIT_ENOTFOUND) goto cleanup; - if (!(error = git_sortedcache_lookup_index( - &pack_pos, backend->refcache, ref_name))) - error = git_sortedcache_remove(backend->refcache, pack_pos); - - git_sortedcache_wunlock(backend->refcache); - if (error == GIT_ENOTFOUND) { - error = loose_deleted ? 0 : ref_error_notfound(ref_name); + error = packed_deleted ? 0 : ref_error_notfound(ref_name); goto cleanup; } - error = packed_write(backend); - cleanup: - git_buf_dispose(&loose_path); git_filebuf_cleanup(file); - if (loose_deleted) - refdb_fs_backend__try_delete_empty_ref_hierarchie(backend, ref_name, false); + if (error == 0) + refdb_fs_backend__prune_refs(backend, ref_name, ""); return error; } @@ -1446,8 +1501,8 @@ static int refdb_fs_backend__rename( const git_signature *who, const char *message) { - refdb_fs_backend *backend = (refdb_fs_backend *)_backend; - git_reference *old, *new; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); + git_reference *old, *new = NULL; git_filebuf file = GIT_FILEBUF_INIT; int error; @@ -1463,7 +1518,7 @@ static int refdb_fs_backend__rename( return error; } - new = git_reference__set_name(old, new_name); + new = git_reference__realloc(&old, new_name); if (!new) { git_reference_free(old); return -1; @@ -1502,7 +1557,7 @@ static int refdb_fs_backend__rename( static int refdb_fs_backend__compress(git_refdb_backend *_backend) { int error; - refdb_fs_backend *backend = (refdb_fs_backend *)_backend; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); assert(backend); @@ -1516,7 +1571,7 @@ static int refdb_fs_backend__compress(git_refdb_backend *_backend) static void refdb_fs_backend__free(git_refdb_backend *_backend) { - refdb_fs_backend *backend = (refdb_fs_backend *)_backend; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); assert(backend); @@ -1597,70 +1652,57 @@ static int reflog_alloc(git_reflog **reflog, const char *name) static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) { - const char *ptr; - git_reflog_entry *entry; + git_parse_ctx parser = GIT_PARSE_CTX_INIT; -#define seek_forward(_increase) do { \ - if (_increase >= buf_size) { \ - git_error_set(GIT_ERROR_INVALID, "ran out of data while parsing reflog"); \ - goto fail; \ - } \ - buf += _increase; \ - buf_size -= _increase; \ - } while (0) + if ((git_parse_ctx_init(&parser, buf, buf_size)) < 0) + return -1; - while (buf_size > GIT_REFLOG_SIZE_MIN) { - entry = git__calloc(1, sizeof(git_reflog_entry)); + for (; parser.remain_len; git_parse_advance_line(&parser)) { + git_reflog_entry *entry; + const char *sig; + char c; + + entry = git__calloc(1, sizeof(*entry)); GIT_ERROR_CHECK_ALLOC(entry); - - entry->committer = git__calloc(1, sizeof(git_signature)); + entry->committer = git__calloc(1, sizeof(*entry->committer)); GIT_ERROR_CHECK_ALLOC(entry->committer); - if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0) - goto fail; - seek_forward(GIT_OID_HEXSZ + 1); + if (git_parse_advance_oid(&entry->oid_old, &parser) < 0 || + git_parse_advance_expected(&parser, " ", 1) < 0 || + git_parse_advance_oid(&entry->oid_cur, &parser) < 0) + goto next; - if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0) - goto fail; - seek_forward(GIT_OID_HEXSZ + 1); + sig = parser.line; + while (git_parse_peek(&c, &parser, 0) == 0 && c != '\t' && c != '\n') + git_parse_advance_chars(&parser, 1); - ptr = buf; + if (git_signature__parse(entry->committer, &sig, parser.line, NULL, 0) < 0) + goto next; - /* Seek forward to the end of the signature. */ - while (*buf && *buf != '\t' && *buf != '\n') - seek_forward(1); + if (c == '\t') { + size_t len; + git_parse_advance_chars(&parser, 1); - if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0) - goto fail; + len = parser.line_len; + if (parser.line[len - 1] == '\n') + len--; - if (*buf == '\t') { - /* We got a message. Read everything till we reach LF. */ - seek_forward(1); - ptr = buf; - - while (*buf && *buf != '\n') - seek_forward(1); - - entry->msg = git__strndup(ptr, buf - ptr); + entry->msg = git__strndup(parser.line, len); GIT_ERROR_CHECK_ALLOC(entry->msg); - } else - entry->msg = NULL; + } - while (*buf && *buf == '\n' && buf_size > 1) - seek_forward(1); + if ((git_vector_insert(&log->entries, entry)) < 0) { + git_reflog_entry__free(entry); + return -1; + } - if (git_vector_insert(&log->entries, entry) < 0) - goto fail; + continue; + +next: + git_reflog_entry__free(entry); } return 0; - -#undef seek_forward - -fail: - git_reflog_entry__free(entry); - - return -1; } static int create_new_reflog_file(const char *filepath) @@ -1694,7 +1736,7 @@ static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char * assert(_backend && name); - backend = (refdb_fs_backend *) _backend; + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); repo = backend->repo; if ((error = retrieve_reflog_path(&path, repo, name)) < 0) @@ -1727,7 +1769,7 @@ static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *nam assert(_backend && name); - backend = (refdb_fs_backend *) _backend; + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); return has_reflog(backend->repo, name); } @@ -1743,7 +1785,7 @@ static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, assert(out && _backend && name); - backend = (refdb_fs_backend *) _backend; + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); repo = backend->repo; if (reflog_alloc(&log, name) < 0) @@ -1802,8 +1844,15 @@ static int serialize_reflog_entry( git_buf_rtrim(buf); if (msg) { + size_t i; + git_buf_putc(buf, '\t'); git_buf_puts(buf, msg); + + for (i = 0; i < buf->size - 2; i++) + if (buf->ptr[i] == '\n') + buf->ptr[i] = ' '; + git_buf_rtrim(buf); } git_buf_putc(buf, '\n'); @@ -1853,7 +1902,7 @@ static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflo assert(_backend && reflog); - backend = (refdb_fs_backend *) _backend; + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0) return -1; @@ -1894,7 +1943,7 @@ static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, co !(old && new)) return 0; - /* From here on is_symoblic also means that it's HEAD */ + /* From here on is_symbolic also means that it's HEAD */ if (old) { git_oid_cpy(&old_id, old); @@ -1975,7 +2024,7 @@ static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_ assert(_backend && old_name && new_name); - backend = (refdb_fs_backend *) _backend; + backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); repo = backend->repo; if ((error = git_reference__normalize_name( @@ -2046,7 +2095,7 @@ cleanup: static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name) { - refdb_fs_backend *backend = (refdb_fs_backend *) _backend; + refdb_fs_backend *backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent); git_buf path = GIT_BUF_INIT; int error; @@ -2061,7 +2110,7 @@ static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name if ((error = p_unlink(path.ptr)) < 0) goto out; - refdb_fs_backend__try_delete_empty_ref_hierarchie(backend, name, true); + refdb_fs_backend__prune_refs(backend, name, GIT_REFLOG_DIR); out: git_buf_dispose(&path); @@ -2080,6 +2129,9 @@ int git_refdb_backend_fs( backend = git__calloc(1, sizeof(refdb_fs_backend)); GIT_ERROR_CHECK_ALLOC(backend); + if (git_refdb_init_backend(&backend->parent, GIT_REFDB_BACKEND_VERSION) < 0) + goto fail; + backend->repo = repository; if (repository->gitdir) { @@ -2104,15 +2156,15 @@ int git_refdb_backend_fs( git_buf_dispose(&gitpath); - if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) { + if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_IGNORECASE) && t) { backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE; backend->direach_flags |= GIT_PATH_DIR_IGNORE_CASE; } - if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_PRECOMPOSE) && t) { + if (!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_PRECOMPOSE) && t) { backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; backend->direach_flags |= GIT_PATH_DIR_PRECOMPOSE_UNICODE; } - if ((!git_repository__cvar(&t, backend->repo, GIT_CVAR_FSYNCOBJECTFILES) && t) || + if ((!git_repository__configmap_lookup(&t, backend->repo, GIT_CONFIGMAP_FSYNCOBJECTFILES) && t) || git_repository__fsync_gitdir) backend->fsync = 1; backend->iterator_flags |= GIT_ITERATOR_DESCEND_SYMLINKS; diff --git a/src/reflog.c b/src/reflog.c index 1834a2736..24dada047 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -74,9 +74,8 @@ int git_reflog_write(git_reflog *reflog) int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg) { - git_reflog_entry *entry; const git_reflog_entry *previous; - const char *newline; + git_reflog_entry *entry; assert(reflog && new_oid && committer); @@ -87,19 +86,18 @@ int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_sign goto cleanup; if (msg != NULL) { - if ((entry->msg = git__strdup(msg)) == NULL) + size_t i, msglen = strlen(msg); + + if ((entry->msg = git__strndup(msg, msglen)) == NULL) goto cleanup; - newline = strchr(msg, '\n'); - - if (newline) { - if (newline[1] != '\0') { - git_error_set(GIT_ERROR_INVALID, "reflog message cannot contain newline"); - goto cleanup; - } - - entry->msg[newline - msg] = '\0'; - } + /* + * Replace all newlines with spaces, except for + * the final trailing newline. + */ + for (i = 0; i < msglen; i++) + if (entry->msg[i] == '\n') + entry->msg[i] = ' '; } previous = git_reflog_entry_byindex(reflog, 0); diff --git a/src/refs.c b/src/refs.c index 644bc2e68..633d83abd 100644 --- a/src/refs.c +++ b/src/refs.c @@ -9,7 +9,7 @@ #include "hash.h" #include "repository.h" -#include "fileops.h" +#include "futils.h" #include "filebuf.h" #include "pack.h" #include "reflog.h" @@ -91,18 +91,23 @@ git_reference *git_reference__alloc( return ref; } -git_reference *git_reference__set_name( - git_reference *ref, const char *name) +git_reference *git_reference__realloc( + git_reference **ptr_to_ref, const char *name) { - size_t namelen = strlen(name); - size_t reflen; + size_t namelen, reflen; git_reference *rewrite = NULL; + assert(ptr_to_ref && name); + + namelen = strlen(name); + if (!GIT_ADD_SIZET_OVERFLOW(&reflen, sizeof(git_reference), namelen) && !GIT_ADD_SIZET_OVERFLOW(&reflen, reflen, 1) && - (rewrite = git__realloc(ref, reflen)) != NULL) + (rewrite = git__realloc(*ptr_to_ref, reflen)) != NULL) memcpy(rewrite->name, name, namelen + 1); + *ptr_to_ref = NULL; + return rewrite; } @@ -140,6 +145,11 @@ int git_reference_delete(git_reference *ref) const git_oid *old_id = NULL; const char *old_target = NULL; + if (!strcmp(ref->name, "HEAD")) { + git_error_set(GIT_ERROR_REFERENCE, "cannot delete HEAD"); + return GIT_ERROR; + } + if (ref->type == GIT_REFERENCE_DIRECT) old_id = &ref->target.oid; else @@ -188,7 +198,7 @@ static int reference_normalize_for_repo( int precompose; unsigned int flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL; - if (!git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) && + if (!git_repository__configmap_lookup(&precompose, repo, GIT_CONFIGMAP_PRECOMPOSE) && precompose) flags |= GIT_REFERENCE_FORMAT__PRECOMPOSE_UNICODE; @@ -386,7 +396,7 @@ const git_oid *git_reference_target_peel(const git_reference *ref) { assert(ref); - if (ref->type != GIT_REFERENCE_DIRECT || git_oid_iszero(&ref->peel)) + if (ref->type != GIT_REFERENCE_DIRECT || git_oid_is_zero(&ref->peel)) return NULL; return &ref->peel; @@ -692,7 +702,7 @@ static int reference__rename(git_reference **out, git_reference *ref, const char payload.old_name = ref->name; memcpy(&payload.new_name, &normalized, sizeof(normalized)); - error = git_repository_foreach_head(repo, update_wt_heads, &payload); + error = git_repository_foreach_head(repo, update_wt_heads, 0, &payload); } return error; @@ -897,14 +907,13 @@ static int is_valid_ref_char(char ch) case '\\': case '?': case '[': - case '*': return 0; default: return 1; } } -static int ensure_segment_validity(const char *name) +static int ensure_segment_validity(const char *name, char may_contain_glob) { const char *current = name; char prev = '\0'; @@ -927,6 +936,12 @@ static int ensure_segment_validity(const char *name) if (prev == '@' && *current == '{') return -1; /* Refname contains "@{" */ + if (*current == '*') { + if (!may_contain_glob) + return -1; + may_contain_glob = 0; + } + prev = *current; } @@ -1005,19 +1020,20 @@ int git_reference__normalize_name( } while (true) { - segment_len = ensure_segment_validity(current); - if (segment_len < 0) { - if ((process_flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN) && - current[0] == '*' && - (current[1] == '\0' || current[1] == '/')) { - /* Accept one wildcard as a full refname component. */ - process_flags &= ~GIT_REFERENCE_FORMAT_REFSPEC_PATTERN; - segment_len = 1; - } else - goto cleanup; - } + char may_contain_glob = process_flags & GIT_REFERENCE_FORMAT_REFSPEC_PATTERN; + + segment_len = ensure_segment_validity(current, may_contain_glob); + if (segment_len < 0) + goto cleanup; if (segment_len > 0) { + /* + * There may only be one glob in a pattern, thus we reset + * the pattern-flag in case the current segment has one. + */ + if (memchr(current, '*', segment_len)) + process_flags &= ~GIT_REFERENCE_FORMAT_REFSPEC_PATTERN; + if (normalize) { size_t cur_len = git_buf_len(buf); @@ -1374,7 +1390,7 @@ int git_reference_peel( * to a commit. So we only want to use the peeled value * if it is not zero and the target is not a tag. */ - if (target_type != GIT_OBJECT_TAG && !git_oid_iszero(&resolved->peel)) { + if (target_type != GIT_OBJECT_TAG && !git_oid_is_zero(&resolved->peel)) { error = git_object_lookup(&target, git_reference_owner(ref), &resolved->peel, GIT_OBJECT_ANY); } else { diff --git a/src/refs.h b/src/refs.h index 46df95eba..adc345a12 100644 --- a/src/refs.h +++ b/src/refs.h @@ -75,7 +75,14 @@ struct git_reference { char name[GIT_FLEX_ARRAY]; }; -git_reference *git_reference__set_name(git_reference *ref, const char *name); +/** + * Reallocate the reference with a new name + * + * Note that this is a dangerous operation, as on success, all existing + * pointers to the old reference will now be dangling. Only call this on objects + * you control, possibly using `git_reference_dup`. + */ +git_reference *git_reference__realloc(git_reference **ptr_to_ref, const char *name); int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags); int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid, const git_signature *sig, const char *log_message); diff --git a/src/refspec.c b/src/refspec.c index eaf0c2c83..854240a84 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -9,10 +9,10 @@ #include "git2/errors.h" -#include "util.h" -#include "posix.h" #include "refs.h" +#include "util.h" #include "vector.h" +#include "wildmatch.h" int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) { @@ -213,7 +213,7 @@ int git_refspec_src_matches(const git_refspec *refspec, const char *refname) if (refspec == NULL || refspec->src == NULL) return false; - return (p_fnmatch(refspec->src, refname, 0) == 0); + return (wildmatch(refspec->src, refname, 0) == 0); } int git_refspec_dst_matches(const git_refspec *refspec, const char *refname) @@ -221,14 +221,13 @@ int git_refspec_dst_matches(const git_refspec *refspec, const char *refname) if (refspec == NULL || refspec->dst == NULL) return false; - return (p_fnmatch(refspec->dst, refname, 0) == 0); + return (wildmatch(refspec->dst, refname, 0) == 0); } static int refspec_transform( git_buf *out, const char *from, const char *to, const char *name) { const char *from_star, *to_star; - const char *name_slash, *from_slash; size_t replacement_len, star_offset; git_buf_sanitize(out); @@ -251,17 +250,11 @@ static int refspec_transform( /* the first half is copied over */ git_buf_put(out, to, to_star - to); - /* then we copy over the replacement, from the star's offset to the next slash in 'name' */ - name_slash = strchr(name + star_offset, '/'); - if (!name_slash) - name_slash = strrchr(name, '\0'); - - /* if there is no slash after the star in 'from', we want to copy everything over */ - from_slash = strchr(from + star_offset, '/'); - if (!from_slash) - name_slash = strrchr(name, '\0'); - - replacement_len = (name_slash - name) - star_offset; + /* + * Copy over the name, but exclude the trailing part in "from" starting + * after the glob + */ + replacement_len = strlen(name + star_offset) - strlen(from_star + 1); git_buf_put(out, name + star_offset, replacement_len); return git_buf_puts(out, to_star + 1); diff --git a/src/regexp.c b/src/regexp.c new file mode 100644 index 000000000..5fe49f37e --- /dev/null +++ b/src/regexp.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "regexp.h" + +#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE) + +int git_regexp_compile(git_regexp *r, const char *pattern, int flags) +{ + int erroffset, cflags = 0; + const char *error; + + if (flags & GIT_REGEXP_ICASE) + cflags |= PCRE_CASELESS; + + if ((*r = pcre_compile(pattern, cflags, &error, &erroffset, NULL)) == NULL) { + git_error_set_str(GIT_ERROR_REGEX, error); + return GIT_EINVALIDSPEC; + } + + return 0; +} + +void git_regexp_dispose(git_regexp *r) +{ + pcre_free(*r); + *r = NULL; +} + +int git_regexp_match(const git_regexp *r, const char *string) +{ + int error; + if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, NULL, 0)) < 0) + return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) +{ + int static_ovec[9], *ovec; + int error; + size_t i; + + /* The ovec array always needs to be a mutiple of three */ + if (nmatches <= ARRAY_SIZE(static_ovec) / 3) + ovec = static_ovec; + else + ovec = git__calloc(nmatches * 3, sizeof(*ovec)); + GIT_ERROR_CHECK_ALLOC(ovec); + + if ((error = pcre_exec(*r, NULL, string, (int) strlen(string), 0, 0, ovec, (int) nmatches * 3)) < 0) + goto out; + + if (error == 0) + error = (int) nmatches; + + for (i = 0; i < (unsigned int) error; i++) { + matches[i].start = (ovec[i * 2] < 0) ? -1 : ovec[i * 2]; + matches[i].end = (ovec[i * 2 + 1] < 0) ? -1 : ovec[i * 2 + 1]; + } + for (i = (unsigned int) error; i < nmatches; i++) + matches[i].start = matches[i].end = -1; + +out: + if (nmatches > ARRAY_SIZE(static_ovec) / 3) + git__free(ovec); + if (error < 0) + return (error == PCRE_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +#elif defined(GIT_REGEX_PCRE2) + +int git_regexp_compile(git_regexp *r, const char *pattern, int flags) +{ + unsigned char errmsg[1024]; + PCRE2_SIZE erroff; + int error, cflags = 0; + + if (flags & GIT_REGEXP_ICASE) + cflags |= PCRE2_CASELESS; + + if ((*r = pcre2_compile((const unsigned char *) pattern, PCRE2_ZERO_TERMINATED, + cflags, &error, &erroff, NULL)) == NULL) { + pcre2_get_error_message(error, errmsg, sizeof(errmsg)); + git_error_set_str(GIT_ERROR_REGEX, (char *) errmsg); + return GIT_EINVALIDSPEC; + } + + return 0; +} + +void git_regexp_dispose(git_regexp *r) +{ + pcre2_code_free(*r); + *r = NULL; +} + +int git_regexp_match(const git_regexp *r, const char *string) +{ + pcre2_match_data *data; + int error; + + data = pcre2_match_data_create(1, NULL); + GIT_ERROR_CHECK_ALLOC(data); + + if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), + 0, 0, data, NULL)) < 0) + return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + + pcre2_match_data_free(data); + return 0; +} + +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) +{ + pcre2_match_data *data = NULL; + PCRE2_SIZE *ovec; + int error; + size_t i; + + if ((data = pcre2_match_data_create(nmatches, NULL)) == NULL) { + git_error_set_oom(); + goto out; + } + + if ((error = pcre2_match(*r, (const unsigned char *) string, strlen(string), + 0, 0, data, NULL)) < 0) + goto out; + + if (error == 0 || (unsigned int) error > nmatches) + error = nmatches; + ovec = pcre2_get_ovector_pointer(data); + + for (i = 0; i < (unsigned int) error; i++) { + matches[i].start = (ovec[i * 2] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2]; + matches[i].end = (ovec[i * 2 + 1] == PCRE2_UNSET) ? -1 : (ssize_t) ovec[i * 2 + 1]; + } + for (i = (unsigned int) error; i < nmatches; i++) + matches[i].start = matches[i].end = -1; + +out: + pcre2_match_data_free(data); + if (error < 0) + return (error == PCRE2_ERROR_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L) + +#if defined(GIT_REGEX_REGCOMP_L) +# include +#endif + +int git_regexp_compile(git_regexp *r, const char *pattern, int flags) +{ + int cflags = REG_EXTENDED, error; + char errmsg[1024]; + + if (flags & GIT_REGEXP_ICASE) + cflags |= REG_ICASE; + +# if defined(GIT_REGEX_REGCOMP) + if ((error = regcomp(r, pattern, cflags)) != 0) +# else + if ((error = regcomp_l(r, pattern, cflags, (locale_t) 0)) != 0) +# endif + { + regerror(error, r, errmsg, sizeof(errmsg)); + git_error_set_str(GIT_ERROR_REGEX, errmsg); + return GIT_EINVALIDSPEC; + } + + return 0; +} + +void git_regexp_dispose(git_regexp *r) +{ + regfree(r); +} + +int git_regexp_match(const git_regexp *r, const char *string) +{ + int error; + if ((error = regexec(r, string, 0, NULL, 0)) != 0) + return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches) +{ + regmatch_t static_m[3], *m; + int error; + size_t i; + + if (nmatches <= ARRAY_SIZE(static_m)) + m = static_m; + else + m = git__calloc(nmatches, sizeof(*m)); + + if ((error = regexec(r, string, nmatches, m, 0)) != 0) + goto out; + + for (i = 0; i < nmatches; i++) { + matches[i].start = (m[i].rm_so < 0) ? -1 : m[i].rm_so; + matches[i].end = (m[i].rm_eo < 0) ? -1 : m[i].rm_eo; + } + +out: + if (nmatches > ARRAY_SIZE(static_m)) + git__free(m); + if (error) + return (error == REG_NOMATCH) ? GIT_ENOTFOUND : GIT_EINVALIDSPEC; + return 0; +} + +#endif diff --git a/src/regexp.h b/src/regexp.h new file mode 100644 index 000000000..2592ef383 --- /dev/null +++ b/src/regexp.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_regexp_h__ +#define INCLUDE_regexp_h__ + +#include "common.h" + +#if defined(GIT_REGEX_BUILTIN) || defined(GIT_REGEX_PCRE) +# include "pcre.h" +typedef pcre *git_regexp; +# define GIT_REGEX_INIT NULL +#elif defined(GIT_REGEX_PCRE2) +# define PCRE2_CODE_UNIT_WIDTH 8 +# include +typedef pcre2_code *git_regexp; +# define GIT_REGEX_INIT NULL +#elif defined(GIT_REGEX_REGCOMP) || defined(GIT_REGEX_REGCOMP_L) +# include +typedef regex_t git_regexp; +# define GIT_REGEX_INIT { 0 } +#else +# error "No regex backend" +#endif + +/** Options supported by @git_regexp_compile. */ +typedef enum { + /** Enable case-insensitive matching */ + GIT_REGEXP_ICASE = (1 << 0) +} git_regexp_flags_t; + +/** Structure containing information about regular expression matching groups */ +typedef struct { + /** Start of the given match. -1 if the group didn't match anything */ + ssize_t start; + /** End of the given match. -1 if the group didn't match anything */ + ssize_t end; +} git_regmatch; + +/** + * Compile a regular expression. The compiled expression needs to + * be cleaned up afterwards with `git_regexp_dispose`. + * + * @param r Pointer to the storage where to initialize the regular expression. + * @param pattern The pattern that shall be compiled. + * @param flags Flags to alter how the pattern shall be handled. + * 0 for defaults, otherwise see @git_regexp_flags_t. + * @return 0 on success, otherwise a negative return value. + */ +int git_regexp_compile(git_regexp *r, const char *pattern, int flags); + +/** + * Free memory associated with the regular expression + * + * @param r The regular expression structure to dispose. + */ +void git_regexp_dispose(git_regexp *r); + +/** + * Test whether a given string matches a compiled regular + * expression. + * + * @param r Compiled regular expression. + * @param string String to match against the regular expression. + * @return 0 if the string matches, a negative error code + * otherwise. GIT_ENOTFOUND if no match was found, + * GIT_EINVALIDSPEC if the regular expression matching + * was invalid. + */ +int git_regexp_match(const git_regexp *r, const char *string); + +/** + * Search for matches inside of a given string. + * + * Given a regular expression with capturing groups, this + * function will populate provided @git_regmatch structures with + * offsets for each of the given matches. Non-matching groups + * will have start and end values of the respective @git_regmatch + * structure set to -1. + * + * @param r Compiled regular expression. + * @param string String to match against the regular expression. + * @param nmatches Number of @git_regmatch structures provided by + * the user. + * @param matches Pointer to an array of @git_regmatch structures. + * @return 0 if the string matches, a negative error code + * otherwise. GIT_ENOTFOUND if no match was found, + * GIT_EINVALIDSPEC if the regular expression matching + * was invalid. + */ +int git_regexp_search(const git_regexp *r, const char *string, size_t nmatches, git_regmatch *matches); + +#endif diff --git a/src/remote.c b/src/remote.c index 2f0b8c5ee..740dd9434 100644 --- a/src/remote.c +++ b/src/remote.c @@ -136,35 +136,6 @@ cleanup: return 0; } -#if 0 -/* We could export this as a helper */ -static int get_check_cert(int *out, git_repository *repo) -{ - git_config *cfg; - const char *val; - int error = 0; - - assert(out && repo); - - /* By default, we *DO* want to verify the certificate. */ - *out = 1; - - /* Go through the possible sources for SSL verification settings, from - * most specific to least specific. */ - - /* GIT_SSL_NO_VERIFY environment variable */ - if ((val = p_getenv("GIT_SSL_NO_VERIFY")) != NULL) - return git_config_parse_bool(out, val); - - /* http.sslVerify config setting */ - if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) - return error; - - *out = git_config__get_bool_force(cfg, "http.sslverify", 1); - return 0; -} -#endif - static int canonicalize_url(git_buf *out, const char *in) { if (in == NULL || strlen(in) == 0) { @@ -217,13 +188,18 @@ static int ensure_remote_doesnot_exist(git_repository *repo, const char *name) return GIT_EEXISTS; } -int git_remote_create_init_options(git_remote_create_options *opts, unsigned int version) +int git_remote_create_options_init(git_remote_create_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_remote_create_options, GIT_REMOTE_CREATE_OPTIONS_INIT); return 0; } +int git_remote_create_init_options(git_remote_create_options *opts, unsigned int version) +{ + return git_remote_create_options_init(opts, version); +} + int git_remote_create_with_opts(git_remote **out, const char *url, const git_remote_create_options *opts) { git_remote *remote = NULL; @@ -670,21 +646,44 @@ int git_remote_set_pushurl(git_repository *repo, const char *remote, const char* return set_url(repo, remote, CONFIG_PUSHURL_FMT, url); } -const char* git_remote__urlfordirection(git_remote *remote, int direction) +static int resolve_url(git_buf *resolved_url, const char *url, int direction, const git_remote_callbacks *callbacks) { - assert(remote); + int status; + if (callbacks && callbacks->resolve_url) { + git_buf_clear(resolved_url); + status = callbacks->resolve_url(resolved_url, url, direction, callbacks->payload); + if (status != GIT_PASSTHROUGH) { + git_error_set_after_callback_function(status, "git_resolve_url_cb"); + git_buf_sanitize(resolved_url); + return status; + } + } + + return git_buf_sets(resolved_url, url); +} + +int git_remote__urlfordirection(git_buf *url_out, struct git_remote *remote, int direction, const git_remote_callbacks *callbacks) +{ + const char *url = NULL; + + assert(remote); assert(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH); if (direction == GIT_DIRECTION_FETCH) { - return remote->url; + url = remote->url; + } else if (direction == GIT_DIRECTION_PUSH) { + url = remote->pushurl ? remote->pushurl : remote->url; } - if (direction == GIT_DIRECTION_PUSH) { - return remote->pushurl ? remote->pushurl : remote->url; + if (!url) { + git_error_set(GIT_ERROR_INVALID, + "malformed remote '%s' - missing %s URL", + remote->name ? remote->name : "(anonymous)", + direction == GIT_DIRECTION_FETCH ? "fetch" : "push"); + return GIT_EINVALID; } - - return NULL; + return resolve_url(url_out, url, direction, callbacks); } int set_transport_callbacks(git_transport *t, const git_remote_callbacks *cbs) @@ -707,11 +706,11 @@ static int set_transport_custom_headers(git_transport *t, const git_strarray *cu int git_remote__connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_remote_connection_opts *conn) { git_transport *t; - const char *url; + git_buf url = GIT_BUF_INIT; int flags = GIT_TRANSPORTFLAGS_NONE; int error; void *payload = NULL; - git_cred_acquire_cb credentials = NULL; + git_credential_acquire_cb credentials = NULL; git_transport_cb transport = NULL; assert(remote); @@ -728,39 +727,38 @@ int git_remote__connect(git_remote *remote, git_direction direction, const git_r t = remote->transport; - url = git_remote__urlfordirection(remote, direction); - if (url == NULL) { - git_error_set(GIT_ERROR_INVALID, - "Malformed remote '%s' - missing %s URL", - remote->name ? remote->name : "(anonymous)", - direction == GIT_DIRECTION_FETCH ? "fetch" : "push"); - return -1; - } + if ((error = git_remote__urlfordirection(&url, remote, direction, callbacks)) < 0) + goto on_error; /* If we don't have a transport object yet, and the caller specified a * custom transport factory, use that */ if (!t && transport && (error = transport(&t, remote, payload)) < 0) - return error; + goto on_error; /* If we still don't have a transport, then use the global * transport registrations which map URI schemes to transport factories */ - if (!t && (error = git_transport_new(&t, remote, url)) < 0) - return error; + if (!t && (error = git_transport_new(&t, remote, url.ptr)) < 0) + goto on_error; if ((error = set_transport_custom_headers(t, conn->custom_headers)) != 0) goto on_error; if ((error = set_transport_callbacks(t, callbacks)) < 0 || - (error = t->connect(t, url, credentials, payload, conn->proxy, direction, flags)) != 0) + (error = t->connect(t, url.ptr, credentials, payload, conn->proxy, direction, flags)) != 0) goto on_error; remote->transport = t; + git_buf_dispose(&url); + return 0; on_error: - t->free(t); + if (t) + t->free(t); + + git_buf_dispose(&url); if (t == remote->transport) remote->transport = NULL; @@ -1678,20 +1676,24 @@ int git_remote_connected(const git_remote *remote) return remote->transport->is_connected(remote->transport); } -void git_remote_stop(git_remote *remote) +int git_remote_stop(git_remote *remote) { assert(remote); if (remote->transport && remote->transport->cancel) remote->transport->cancel(remote->transport); + + return 0; } -void git_remote_disconnect(git_remote *remote) +int git_remote_disconnect(git_remote *remote) { assert(remote); if (git_remote_connected(remote)) remote->transport->close(remote->transport); + + return 0; } void git_remote_free(git_remote *remote) @@ -1770,7 +1772,7 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo) return 0; } -const git_transfer_progress* git_remote_stats(git_remote *remote) +const git_indexer_progress *git_remote_stats(git_remote *remote) { assert(remote); return &remote->stats; diff --git a/src/remote.h b/src/remote.h index 62bd1d2ae..df75ed359 100644 --- a/src/remote.h +++ b/src/remote.h @@ -29,7 +29,7 @@ struct git_remote { git_transport *transport; git_repository *repo; git_push *push; - git_transfer_progress stats; + git_indexer_progress stats; unsigned int need_pack; git_remote_autotag_option_t download_tags; int prune_refs; @@ -45,7 +45,7 @@ typedef struct git_remote_connection_opts { int git_remote__connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_remote_connection_opts *conn); -const char* git_remote__urlfordirection(struct git_remote *remote, int direction); +int git_remote__urlfordirection(git_buf *url_out, struct git_remote *remote, int direction, const git_remote_callbacks *callbacks); int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url); git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname); diff --git a/src/repository.c b/src/repository.c index 26936a82f..fe0d696c6 100644 --- a/src/repository.c +++ b/src/repository.c @@ -16,7 +16,7 @@ #include "commit.h" #include "tag.h" #include "blob.h" -#include "fileops.h" +#include "futils.h" #include "sysdir.h" #include "filebuf.h" #include "index.h" @@ -42,23 +42,24 @@ bool git_repository__fsync_gitdir = false; static const struct { git_repository_item_t parent; + git_repository_item_t fallback; const char *name; bool directory; } items[] = { - { GIT_REPOSITORY_ITEM_GITDIR, NULL, true }, - { GIT_REPOSITORY_ITEM_WORKDIR, NULL, true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, NULL, true }, - { GIT_REPOSITORY_ITEM_GITDIR, "index", false }, - { GIT_REPOSITORY_ITEM_COMMONDIR, "objects", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, "refs", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, "packed-refs", false }, - { GIT_REPOSITORY_ITEM_COMMONDIR, "remotes", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, "config", false }, - { GIT_REPOSITORY_ITEM_COMMONDIR, "info", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, "hooks", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, "logs", true }, - { GIT_REPOSITORY_ITEM_GITDIR, "modules", true }, - { GIT_REPOSITORY_ITEM_COMMONDIR, "worktrees", true } + { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true }, + { GIT_REPOSITORY_ITEM_WORKDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM__LAST, NULL, true }, + { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, "index", false }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "objects", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "refs", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "packed-refs", false }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "remotes", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "config", false }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "info", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "hooks", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "logs", true }, + { GIT_REPOSITORY_ITEM_GITDIR, GIT_REPOSITORY_ITEM__LAST, "modules", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, GIT_REPOSITORY_ITEM_GITDIR, "worktrees", true } }; static int check_repositoryformatversion(git_config *config); @@ -121,7 +122,7 @@ static void set_config(git_repository *repo, git_config *config) git_config_free(config); } - git_repository__cvar_cache_clear(repo); + git_repository__configmap_lookup_cache_clear(repo); } static void set_index(git_repository *repo, git_index *index) @@ -137,7 +138,7 @@ static void set_index(git_repository *repo, git_index *index) } } -void git_repository__cleanup(git_repository *repo) +int git_repository__cleanup(git_repository *repo) { assert(repo); @@ -149,6 +150,8 @@ void git_repository__cleanup(git_repository *repo) set_index(repo, NULL); set_odb(repo, NULL); set_refdb(repo, NULL); + + return 0; } void git_repository_free(git_repository *repo) @@ -160,7 +163,7 @@ void git_repository_free(git_repository *repo) git_repository__cleanup(repo); - git_cache_free(&repo->objects); + git_cache_dispose(&repo->objects); git_diff_driver_registry_free(repo->diff_drivers); repo->diff_drivers = NULL; @@ -186,19 +189,25 @@ void git_repository_free(git_repository *repo) * * Open a repository object from its path */ -static bool valid_repository_path(git_buf *repository_path, git_buf *common_path) +static int is_valid_repository_path(bool *out, git_buf *repository_path, git_buf *common_path) { + int error; + + *out = false; + /* Check if we have a separate commondir (e.g. we have a * worktree) */ if (git_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) { git_buf common_link = GIT_BUF_INIT; - git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE); - git_futils_readbuffer(&common_link, common_link.ptr); + if ((error = git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE)) < 0 || + (error = git_futils_readbuffer(&common_link, common_link.ptr)) < 0) + return error; + git_buf_rtrim(&common_link); - if (git_path_is_relative(common_link.ptr)) { - git_buf_joinpath(common_path, repository_path->ptr, common_link.ptr); + if ((error = git_buf_joinpath(common_path, repository_path->ptr, common_link.ptr)) < 0) + return error; } else { git_buf_swap(common_path, &common_link); } @@ -206,24 +215,26 @@ static bool valid_repository_path(git_buf *repository_path, git_buf *common_path git_buf_dispose(&common_link); } else { - git_buf_set(common_path, repository_path->ptr, repository_path->size); + if ((error = git_buf_set(common_path, repository_path->ptr, repository_path->size)) < 0) + return error; } /* Make sure the commondir path always has a trailing * slash */ if (git_buf_rfind(common_path, '/') != (ssize_t)common_path->size - 1) - git_buf_putc(common_path, '/'); + if ((error = git_buf_putc(common_path, '/')) < 0) + return error; /* Ensure HEAD file exists */ if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false) - return false; - + return 0; /* Check files in common dir */ if (git_path_contains_dir(common_path, GIT_OBJECTS_DIR) == false) - return false; + return 0; if (git_path_contains_dir(common_path, GIT_REFS_DIR) == false) - return false; + return 0; - return true; + *out = true; + return 0; } static git_repository *repository_alloc(void) @@ -238,14 +249,14 @@ static git_repository *repository_alloc(void) if (!repo->reserved_names.ptr) goto on_error; - /* set all the entries in the cvar cache to `unset` */ - git_repository__cvar_cache_clear(repo); + /* set all the entries in the configmap cache to `unset` */ + git_repository__configmap_lookup_cache_clear(repo); return repo; on_error: if (repo) - git_cache_free(&repo->objects); + git_cache_dispose(&repo->objects); git__free(repo); return NULL; @@ -438,15 +449,15 @@ static int find_repo( uint32_t flags, const char *ceiling_dirs) { - int error; git_buf path = GIT_BUF_INIT; git_buf repo_link = GIT_BUF_INIT; git_buf common_link = GIT_BUF_INIT; struct stat st; dev_t initial_device = 0; int min_iterations; - bool in_dot_git; + bool in_dot_git, is_valid; size_t ceiling_offset = 0; + int error; git_buf_clear(gitdir_path); @@ -472,9 +483,8 @@ static int find_repo( for (;;) { if (!(flags & GIT_REPOSITORY_OPEN_NO_DOTGIT)) { if (!in_dot_git) { - error = git_buf_joinpath(&path, path.ptr, DOT_GIT); - if (error < 0) - break; + if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0) + goto out; } in_dot_git = !in_dot_git; } @@ -488,28 +498,33 @@ static int find_repo( break; if (S_ISDIR(st.st_mode)) { - if (valid_repository_path(&path, &common_link)) { - git_path_to_dir(&path); - git_buf_set(gitdir_path, path.ptr, path.size); + if ((error = is_valid_repository_path(&is_valid, &path, &common_link)) < 0) + goto out; + + if (is_valid) { + if ((error = git_path_to_dir(&path)) < 0 || + (error = git_buf_set(gitdir_path, path.ptr, path.size)) < 0) + goto out; if (gitlink_path) - git_buf_attach(gitlink_path, - git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0); + if ((error = git_buf_attach(gitlink_path, git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0)) < 0) + goto out; if (commondir_path) git_buf_swap(&common_link, commondir_path); break; } - } - else if (S_ISREG(st.st_mode) && git__suffixcmp(path.ptr, "/" DOT_GIT) == 0) { - error = read_gitfile(&repo_link, path.ptr); - if (error < 0) - break; - if (valid_repository_path(&repo_link, &common_link)) { + } else if (S_ISREG(st.st_mode) && git__suffixcmp(path.ptr, "/" DOT_GIT) == 0) { + if ((error = read_gitfile(&repo_link, path.ptr)) < 0 || + (error = is_valid_repository_path(&is_valid, &repo_link, &common_link)) < 0) + goto out; + + if (is_valid) { git_buf_swap(gitdir_path, &repo_link); if (gitlink_path) - error = git_buf_put(gitlink_path, path.ptr, path.size); + if ((error = git_buf_put(gitlink_path, path.ptr, path.size)) < 0) + goto out; if (commondir_path) git_buf_swap(&common_link, commondir_path); } @@ -520,10 +535,8 @@ static int find_repo( /* Move up one directory. If we're in_dot_git, we'll search the * parent itself next. If we're !in_dot_git, we'll search .git * in the parent directory next (added at the top of the loop). */ - if (git_path_dirname_r(&path, path.ptr) < 0) { - error = -1; - break; - } + if ((error = git_path_dirname_r(&path, path.ptr)) < 0) + goto out; /* Once we've checked the directory (and .git if applicable), * find the ceiling for a search. */ @@ -531,31 +544,28 @@ static int find_repo( ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs); /* Check if we should stop searching here. */ - if (min_iterations == 0 - && (path.ptr[ceiling_offset] == 0 - || (flags & GIT_REPOSITORY_OPEN_NO_SEARCH))) + if (min_iterations == 0 && + (path.ptr[ceiling_offset] == 0 || (flags & GIT_REPOSITORY_OPEN_NO_SEARCH))) break; } - if (!error && workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { + if (workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { if (!git_buf_len(gitdir_path)) git_buf_clear(workdir_path); - else { - git_path_dirname_r(workdir_path, path.ptr); - git_path_to_dir(workdir_path); - } - if (git_buf_oom(workdir_path)) - return -1; + else if ((error = git_path_dirname_r(workdir_path, path.ptr)) < 0 || + (error = git_path_to_dir(workdir_path)) < 0) + goto out; } /* If we didn't find the repository, and we don't have any other error * to report, report that. */ - if (!git_buf_len(gitdir_path) && !error) { - git_error_set(GIT_ERROR_REPOSITORY, - "could not find repository from '%s'", start_path); + if (!git_buf_len(gitdir_path)) { + git_error_set(GIT_ERROR_REPOSITORY, "could not find repository from '%s'", start_path); error = GIT_ENOTFOUND; + goto out; } +out: git_buf_dispose(&path); git_buf_dispose(&repo_link); git_buf_dispose(&common_link); @@ -566,14 +576,16 @@ int git_repository_open_bare( git_repository **repo_ptr, const char *bare_path) { - int error; git_buf path = GIT_BUF_INIT, common_path = GIT_BUF_INIT; git_repository *repo = NULL; + bool is_valid; + int error; - if ((error = git_path_prettify_dir(&path, bare_path, NULL)) < 0) + if ((error = git_path_prettify_dir(&path, bare_path, NULL)) < 0 || + (error = is_valid_repository_path(&is_valid, &path, &common_path)) < 0) return error; - if (!valid_repository_path(&path, &common_path)) { + if (!is_valid) { git_buf_dispose(&path); git_buf_dispose(&common_path); git_error_set(GIT_ERROR_REPOSITORY, "path is not a repository: %s", bare_path); @@ -799,7 +811,7 @@ int git_repository_open_ext( unsigned is_worktree; git_buf gitdir = GIT_BUF_INIT, workdir = GIT_BUF_INIT, gitlink = GIT_BUF_INIT, commondir = GIT_BUF_INIT; - git_repository *repo; + git_repository *repo = NULL; git_config *config = NULL; if (flags & GIT_REPOSITORY_OPEN_FROM_ENV) @@ -812,7 +824,7 @@ int git_repository_open_ext( &gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs); if (error < 0 || !repo_ptr) - return error; + goto cleanup; repo = repository_alloc(); GIT_ERROR_CHECK_ALLOC(repo); @@ -858,11 +870,13 @@ int git_repository_open_ext( cleanup: git_buf_dispose(&gitdir); git_buf_dispose(&workdir); + git_buf_dispose(&gitlink); + git_buf_dispose(&commondir); git_config_free(config); if (error < 0) git_repository_free(repo); - else + else if (repo_ptr) *repo_ptr = repo; return error; @@ -878,7 +892,8 @@ int git_repository_open_from_worktree(git_repository **repo_out, git_worktree *w { git_buf path = GIT_BUF_INIT; git_repository *repo = NULL; - int len, err; + size_t len; + int err; assert(repo_out && wt); @@ -1066,10 +1081,11 @@ int git_repository_config_snapshot(git_config **out, git_repository *repo) return git_config_snapshot(out, weak); } -void git_repository_set_config(git_repository *repo, git_config *config) +int git_repository_set_config(git_repository *repo, git_config *config) { assert(repo && config); set_config(repo, config); + return 0; } int git_repository_odb__weakptr(git_odb **out, git_repository *repo) @@ -1117,10 +1133,11 @@ int git_repository_odb(git_odb **out, git_repository *repo) return 0; } -void git_repository_set_odb(git_repository *repo, git_odb *odb) +int git_repository_set_odb(git_repository *repo, git_odb *odb) { assert(repo && odb); set_odb(repo, odb); + return 0; } int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo) @@ -1157,10 +1174,11 @@ int git_repository_refdb(git_refdb **out, git_repository *repo) return 0; } -void git_repository_set_refdb(git_repository *repo, git_refdb *refdb) +int git_repository_set_refdb(git_repository *repo, git_refdb *refdb) { assert(repo && refdb); set_refdb(repo, refdb); + return 0; } int git_repository_index__weakptr(git_index **out, git_repository *repo) @@ -1206,10 +1224,11 @@ int git_repository_index(git_index **out, git_repository *repo) return 0; } -void git_repository_set_index(git_repository *repo, git_index *index) +int git_repository_set_index(git_repository *repo, git_index *index) { assert(repo); set_index(repo, index); + return 0; } int git_repository_set_namespace(git_repository *repo, const char *namespace) @@ -1285,8 +1304,8 @@ bool git_repository__reserved_names( int (*prefixcmp)(const char *, const char *); int error, ignorecase; - error = git_repository__cvar( - &ignorecase, repo, GIT_CVAR_IGNORECASE); + error = git_repository__configmap_lookup( + &ignorecase, repo, GIT_CONFIGMAP_IGNORECASE); prefixcmp = (error || ignorecase) ? git__prefixcmp_icase : git__prefixcmp; @@ -1358,10 +1377,11 @@ int git_repository_create_head(const char *git_dir, const char *ref_name) git_buf ref_path = GIT_BUF_INIT; git_filebuf ref = GIT_FILEBUF_INIT; const char *fmt; + int error; - if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 || - git_filebuf_open(&ref, ref_path.ptr, 0, GIT_REFS_FILE_MODE) < 0) - goto fail; + if ((error = git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE)) < 0 || + (error = git_filebuf_open(&ref, ref_path.ptr, 0, GIT_REFS_FILE_MODE)) < 0) + goto out; if (!ref_name) ref_name = GIT_BRANCH_MASTER; @@ -1371,17 +1391,14 @@ int git_repository_create_head(const char *git_dir, const char *ref_name) else fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n"; - if (git_filebuf_printf(&ref, fmt, ref_name) < 0 || - git_filebuf_commit(&ref) < 0) - goto fail; + if ((error = git_filebuf_printf(&ref, fmt, ref_name)) < 0 || + (error = git_filebuf_commit(&ref)) < 0) + goto out; - git_buf_dispose(&ref_path); - return 0; - -fail: +out: git_buf_dispose(&ref_path); git_filebuf_cleanup(&ref); - return -1; + return error; } static bool is_chmod_supported(const char *file_path) @@ -1419,9 +1436,6 @@ static bool are_symlinks_supported(const char *wd_path) git_buf xdg_buf = GIT_BUF_INIT; git_buf system_buf = GIT_BUF_INIT; git_buf programdata_buf = GIT_BUF_INIT; - git_buf path = GIT_BUF_INIT; - int fd; - struct stat st; int symlinks = 0; /* @@ -1448,23 +1462,14 @@ static bool are_symlinks_supported(const char *wd_path) goto done; #endif - if ((fd = git_futils_mktmp(&path, wd_path, 0666)) < 0 || - p_close(fd) < 0 || - p_unlink(path.ptr) < 0 || - p_symlink("testing", path.ptr) < 0 || - p_lstat(path.ptr, &st) < 0) + if (!(symlinks = git_path_supports_symlinks(wd_path))) goto done; - symlinks = (S_ISLNK(st.st_mode) != 0); - - (void)p_unlink(path.ptr); - done: git_buf_dispose(&global_buf); git_buf_dispose(&xdg_buf); git_buf_dispose(&system_buf); git_buf_dispose(&programdata_buf); - git_buf_dispose(&path); git_config_free(config); return symlinks != 0; } @@ -1658,7 +1663,7 @@ int git_repository_reinit_filesystem(git_repository *repo, int recurse) git_config_free(config); git_buf_dispose(&path); - git_repository__cvar_cache_clear(repo); + git_repository__configmap_lookup_cache_clear(repo); if (!repo->is_bare && recurse) (void)git_submodule_foreach(repo, repo_reinit_submodule_fs, NULL); @@ -2056,53 +2061,63 @@ int git_repository_init_ext( const char *given_repo, git_repository_init_options *opts) { - int error; git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT, - common_path = GIT_BUF_INIT; + common_path = GIT_BUF_INIT, head_path = GIT_BUF_INIT; const char *wd; + bool is_valid; + int error; assert(out && given_repo && opts); GIT_ERROR_CHECK_VERSION(opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION, "git_repository_init_options"); - error = repo_init_directories(&repo_path, &wd_path, given_repo, opts); - if (error < 0) - goto cleanup; + if ((error = repo_init_directories(&repo_path, &wd_path, given_repo, opts)) < 0) + goto out; wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_buf_cstr(&wd_path); - if (valid_repository_path(&repo_path, &common_path)) { + if ((error = is_valid_repository_path(&is_valid, &repo_path, &common_path)) < 0) + goto out; + + if (is_valid) { if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) { git_error_set(GIT_ERROR_REPOSITORY, "attempt to reinitialize '%s'", given_repo); error = GIT_EEXISTS; - goto cleanup; + goto out; } opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT; - error = repo_init_config( - repo_path.ptr, wd, opts->flags, opts->mode); + if ((error = repo_init_config(repo_path.ptr, wd, opts->flags, opts->mode)) < 0) + goto out; /* TODO: reinitialize the templates */ + } else { + if ((error = repo_init_structure(repo_path.ptr, wd, opts)) < 0 || + (error = repo_init_config(repo_path.ptr, wd, opts->flags, opts->mode)) < 0 || + (error = git_buf_joinpath(&head_path, repo_path.ptr, GIT_HEAD_FILE)) < 0) + goto out; + + /* + * Only set the new HEAD if the file does not exist already via + * a template or if the caller has explicitly supplied an + * initial HEAD value. + */ + if ((!git_path_exists(head_path.ptr) || opts->initial_head) && + (error = git_repository_create_head(repo_path.ptr, opts->initial_head)) < 0) + goto out; } - else { - if (!(error = repo_init_structure( - repo_path.ptr, wd, opts)) && - !(error = repo_init_config( - repo_path.ptr, wd, opts->flags, opts->mode))) - error = git_repository_create_head( - repo_path.ptr, opts->initial_head); - } - if (error < 0) - goto cleanup; - error = git_repository_open(out, repo_path.ptr); + if ((error = git_repository_open(out, repo_path.ptr)) < 0) + goto out; - if (!error && opts->origin_url) - error = repo_init_create_origin(*out, opts->origin_url); + if (opts->origin_url && + (error = repo_init_create_origin(*out, opts->origin_url)) < 0) + goto out; -cleanup: +out: + git_buf_dispose(&head_path); git_buf_dispose(&common_path); git_buf_dispose(&repo_path); git_buf_dispose(&wd_path); @@ -2210,30 +2225,37 @@ out: return error; } -int git_repository_foreach_head(git_repository *repo, git_repository_foreach_head_cb cb, void *payload) +int git_repository_foreach_head(git_repository *repo, + git_repository_foreach_head_cb cb, + int flags, void *payload) { git_strarray worktrees = GIT_VECTOR_INIT; git_buf path = GIT_BUF_INIT; - int error; + int error = 0; size_t i; - /* Execute callback for HEAD of commondir */ - if ((error = git_buf_joinpath(&path, repo->commondir, GIT_HEAD_FILE)) < 0 || - (error = cb(repo, path.ptr, payload) != 0)) - goto out; - if ((error = git_worktree_list(&worktrees, repo)) < 0) { - error = 0; - goto out; + if (!(flags & GIT_REPOSITORY_FOREACH_HEAD_SKIP_REPO)) { + /* Gather HEAD of main repository */ + if ((error = git_buf_joinpath(&path, repo->commondir, GIT_HEAD_FILE)) < 0 || + (error = cb(repo, path.ptr, payload) != 0)) + goto out; } - /* Execute callback for all worktree HEADs */ - for (i = 0; i < worktrees.count; i++) { - if (get_worktree_file_path(&path, repo, worktrees.strings[i], GIT_HEAD_FILE) < 0) - continue; - - if ((error = cb(repo, path.ptr, payload)) != 0) + if (!(flags & GIT_REPOSITORY_FOREACH_HEAD_SKIP_WORKTREES)) { + if ((error = git_worktree_list(&worktrees, repo)) < 0) { + error = 0; goto out; + } + + /* Gather HEADs of all worktrees */ + for (i = 0; i < worktrees.count; i++) { + if (get_worktree_file_path(&path, repo, worktrees.strings[i], GIT_HEAD_FILE) < 0) + continue; + + if ((error = cb(repo, path.ptr, payload)) != 0) + goto out; + } } out: @@ -2300,11 +2322,11 @@ int git_repository_is_empty(git_repository *repo) return is_empty; } -int git_repository_item_path(git_buf *out, const git_repository *repo, git_repository_item_t item) +static const char *resolved_parent_path(const git_repository *repo, git_repository_item_t item, git_repository_item_t fallback) { const char *parent; - switch (items[item].parent) { + switch (item) { case GIT_REPOSITORY_ITEM_GITDIR: parent = git_repository_path(repo); break; @@ -2316,9 +2338,17 @@ int git_repository_item_path(git_buf *out, const git_repository *repo, git_repos break; default: git_error_set(GIT_ERROR_INVALID, "invalid item directory"); - return -1; + return NULL; } + if (!parent && fallback != GIT_REPOSITORY_ITEM__LAST) + return resolved_parent_path(repo, fallback, GIT_REPOSITORY_ITEM__LAST); + return parent; +} + +int git_repository_item_path(git_buf *out, const git_repository *repo, git_repository_item_t item) +{ + const char *parent = resolved_parent_path(repo, items[item].parent, items[item].fallback); if (parent == NULL) { git_error_set(GIT_ERROR_INVALID, "path cannot exist in repository"); return GIT_ENOTFOUND; @@ -2473,7 +2503,7 @@ int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head git_oid_fmt(orig_head_str, orig_head); if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_ORIG_HEAD_FILE)) == 0 && - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) == 0 && + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_MERGE_FILE_MODE)) == 0 && (error = git_filebuf_printf(&file, "%.*s\n", GIT_OID_HEXSZ, orig_head_str)) == 0) error = git_filebuf_commit(&file); @@ -2533,7 +2563,7 @@ int git_repository_hashfile( int error; git_filter_list *fl = NULL; git_file fd = -1; - git_off_t len; + uint64_t len; git_buf full_path = GIT_BUF_INIT; assert(out && path && repo); /* as_path can be NULL */ @@ -2570,11 +2600,8 @@ int git_repository_hashfile( goto cleanup; } - len = git_futils_filesize(fd); - if (len < 0) { - error = (int)len; + if ((error = git_futils_filesize(&len, fd)) < 0) goto cleanup; - } if (!git__is_sizet(len)) { git_error_set(GIT_ERROR_OS, "file size overflow for 32-bit systems"); @@ -2863,7 +2890,7 @@ int git_repository_is_shallow(git_repository *repo) return st.st_size == 0 ? 0 : 1; } -int git_repository_init_init_options( +int git_repository_init_options_init( git_repository_init_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( @@ -2872,6 +2899,12 @@ int git_repository_init_init_options( return 0; } +int git_repository_init_init_options( + git_repository_init_options *opts, unsigned int version) +{ + return git_repository_init_options_init(opts, version); +} + int git_repository_ident(const char **name, const char **email, const git_repository *repo) { *name = repo->ident_name; @@ -2909,7 +2942,7 @@ int git_repository_submodule_cache_all(git_repository *repo) assert(repo); - if ((error = git_strmap_alloc(&repo->submodule_cache))) + if ((error = git_strmap_new(&repo->submodule_cache))) return error; error = git_submodule__map(repo, repo->submodule_cache); diff --git a/src/repository.h b/src/repository.h index 68d7a6c85..bafdb5896 100644 --- a/src/repository.h +++ b/src/repository.h @@ -37,34 +37,34 @@ extern bool git_repository__fsync_gitdir; /** Cvar cache identifiers */ typedef enum { - GIT_CVAR_AUTO_CRLF = 0, /* core.autocrlf */ - GIT_CVAR_EOL, /* core.eol */ - GIT_CVAR_SYMLINKS, /* core.symlinks */ - GIT_CVAR_IGNORECASE, /* core.ignorecase */ - GIT_CVAR_FILEMODE, /* core.filemode */ - GIT_CVAR_IGNORESTAT, /* core.ignorestat */ - GIT_CVAR_TRUSTCTIME, /* core.trustctime */ - GIT_CVAR_ABBREV, /* core.abbrev */ - GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */ - GIT_CVAR_SAFE_CRLF, /* core.safecrlf */ - GIT_CVAR_LOGALLREFUPDATES, /* core.logallrefupdates */ - GIT_CVAR_PROTECTHFS, /* core.protectHFS */ - GIT_CVAR_PROTECTNTFS, /* core.protectNTFS */ - GIT_CVAR_FSYNCOBJECTFILES, /* core.fsyncObjectFiles */ - GIT_CVAR_CACHE_MAX -} git_cvar_cached; + GIT_CONFIGMAP_AUTO_CRLF = 0, /* core.autocrlf */ + GIT_CONFIGMAP_EOL, /* core.eol */ + GIT_CONFIGMAP_SYMLINKS, /* core.symlinks */ + GIT_CONFIGMAP_IGNORECASE, /* core.ignorecase */ + GIT_CONFIGMAP_FILEMODE, /* core.filemode */ + GIT_CONFIGMAP_IGNORESTAT, /* core.ignorestat */ + GIT_CONFIGMAP_TRUSTCTIME, /* core.trustctime */ + GIT_CONFIGMAP_ABBREV, /* core.abbrev */ + GIT_CONFIGMAP_PRECOMPOSE, /* core.precomposeunicode */ + GIT_CONFIGMAP_SAFE_CRLF, /* core.safecrlf */ + GIT_CONFIGMAP_LOGALLREFUPDATES, /* core.logallrefupdates */ + GIT_CONFIGMAP_PROTECTHFS, /* core.protectHFS */ + GIT_CONFIGMAP_PROTECTNTFS, /* core.protectNTFS */ + GIT_CONFIGMAP_FSYNCOBJECTFILES, /* core.fsyncObjectFiles */ + GIT_CONFIGMAP_CACHE_MAX +} git_configmap_item; /** - * CVAR value enumerations + * Configuration map value enumerations * - * These are the values that are actually stored in the cvar cache, instead - * of their string equivalents. These values are internal and symbolic; - * make sure that none of them is set to `-1`, since that is the unique - * identifier for "not cached" + * These are the values that are actually stored in the configmap cache, + * instead of their string equivalents. These values are internal and + * symbolic; make sure that none of them is set to `-1`, since that is + * the unique identifier for "not cached" */ typedef enum { /* The value hasn't been loaded from the cache yet */ - GIT_CVAR_NOT_CACHED = -1, + GIT_CONFIGMAP_NOT_CACHED = -1, /* core.safecrlf: false, 'fail', 'warn' */ GIT_SAFE_CRLF_FALSE = 0, @@ -89,34 +89,34 @@ typedef enum { GIT_EOL_DEFAULT = GIT_EOL_NATIVE, /* core.symlinks: bool */ - GIT_SYMLINKS_DEFAULT = GIT_CVAR_TRUE, + GIT_SYMLINKS_DEFAULT = GIT_CONFIGMAP_TRUE, /* core.ignorecase */ - GIT_IGNORECASE_DEFAULT = GIT_CVAR_FALSE, + GIT_IGNORECASE_DEFAULT = GIT_CONFIGMAP_FALSE, /* core.filemode */ - GIT_FILEMODE_DEFAULT = GIT_CVAR_TRUE, + GIT_FILEMODE_DEFAULT = GIT_CONFIGMAP_TRUE, /* core.ignorestat */ - GIT_IGNORESTAT_DEFAULT = GIT_CVAR_FALSE, + GIT_IGNORESTAT_DEFAULT = GIT_CONFIGMAP_FALSE, /* core.trustctime */ - GIT_TRUSTCTIME_DEFAULT = GIT_CVAR_TRUE, + GIT_TRUSTCTIME_DEFAULT = GIT_CONFIGMAP_TRUE, /* core.abbrev */ GIT_ABBREV_DEFAULT = 7, /* core.precomposeunicode */ - GIT_PRECOMPOSE_DEFAULT = GIT_CVAR_FALSE, + GIT_PRECOMPOSE_DEFAULT = GIT_CONFIGMAP_FALSE, /* core.safecrlf */ - GIT_SAFE_CRLF_DEFAULT = GIT_CVAR_FALSE, + GIT_SAFE_CRLF_DEFAULT = GIT_CONFIGMAP_FALSE, /* core.logallrefupdates */ - GIT_LOGALLREFUPDATES_FALSE = GIT_CVAR_FALSE, - GIT_LOGALLREFUPDATES_TRUE = GIT_CVAR_TRUE, + GIT_LOGALLREFUPDATES_FALSE = GIT_CONFIGMAP_FALSE, + GIT_LOGALLREFUPDATES_TRUE = GIT_CONFIGMAP_TRUE, GIT_LOGALLREFUPDATES_UNSET = 2, GIT_LOGALLREFUPDATES_ALWAYS = 3, GIT_LOGALLREFUPDATES_DEFAULT = GIT_LOGALLREFUPDATES_UNSET, /* core.protectHFS */ - GIT_PROTECTHFS_DEFAULT = GIT_CVAR_FALSE, + GIT_PROTECTHFS_DEFAULT = GIT_CONFIGMAP_FALSE, /* core.protectNTFS */ - GIT_PROTECTNTFS_DEFAULT = GIT_CVAR_TRUE, + GIT_PROTECTNTFS_DEFAULT = GIT_CONFIGMAP_TRUE, /* core.fsyncObjectFiles */ - GIT_FSYNCOBJECTFILES_DEFAULT = GIT_CVAR_FALSE, -} git_cvar_value; + GIT_FSYNCOBJECTFILES_DEFAULT = GIT_CONFIGMAP_FALSE, +} git_configmap_value; /* internal repository init flags */ enum { @@ -154,7 +154,7 @@ struct git_repository { git_atomic attr_session_key; - git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX]; + git_configmap_value configmap_cache[GIT_CONFIGMAP_CACHE_MAX]; git_strmap *submodule_cache; }; @@ -176,6 +176,13 @@ int git_repository_create_head(const char *git_dir, const char *ref_name); */ typedef int (*git_repository_foreach_head_cb)(git_repository *repo, const char *path, void *payload); +enum { + /* Skip enumeration of the main repository HEAD */ + GIT_REPOSITORY_FOREACH_HEAD_SKIP_REPO = (1u << 0), + /* Skip enumeration of worktree HEADs */ + GIT_REPOSITORY_FOREACH_HEAD_SKIP_WORKTREES = (1u << 1), +}; + /* * Iterate over repository and all worktree HEADs. * @@ -184,7 +191,9 @@ typedef int (*git_repository_foreach_head_cb)(git_repository *repo, const char * * executed with the given payload. The return value equals the * return value of the last executed callback function. */ -int git_repository_foreach_head(git_repository *repo, git_repository_foreach_head_cb cb, void *payload); +int git_repository_foreach_head(git_repository *repo, + git_repository_foreach_head_cb cb, + int flags, void *payload); /* * Weak pointers to repository internals. @@ -199,13 +208,13 @@ int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo); int git_repository_index__weakptr(git_index **out, git_repository *repo); /* - * CVAR cache + * Configuration map cache * * Efficient access to the most used config variables of a repository. * The cache is cleared every time the config backend is replaced. */ -int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar); -void git_repository__cvar_cache_clear(git_repository *repo); +int git_repository__configmap_lookup(int *out, git_repository *repo, git_configmap_item item); +void git_repository__configmap_lookup_cache_clear(git_repository *repo); GIT_INLINE(int) git_repository__ensure_not_bare( git_repository *repo, diff --git a/src/revert.c b/src/revert.c index eb71a68db..b41a2a131 100644 --- a/src/revert.c +++ b/src/revert.c @@ -29,7 +29,7 @@ static int write_revert_head( int error = 0; if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_REVERT_HEAD_FILE)) >= 0 && - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) >= 0 && + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_REVERT_FILE_MODE)) >= 0 && (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0) error = git_filebuf_commit(&file); @@ -51,7 +51,7 @@ static int write_merge_msg( int error = 0; if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || - (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) < 0 || + (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_REVERT_FILE_MODE)) < 0 || (error = git_filebuf_printf(&file, "Revert \"%s\"\n\nThis reverts commit %s.\n", commit_msgline, commit_oidstr)) < 0) goto cleanup; @@ -224,9 +224,14 @@ done: return error; } -int git_revert_init_options(git_revert_options *opts, unsigned int version) +int git_revert_options_init(git_revert_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_revert_options, GIT_REVERT_OPTIONS_INIT); return 0; } + +int git_revert_init_options(git_revert_options *opts, unsigned int version) +{ + return git_revert_options_init(opts, version); +} diff --git a/src/revparse.c b/src/revparse.c index 50ee31ca0..c627de67c 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -7,11 +7,10 @@ #include "common.h" -#include - #include "buffer.h" #include "tree.h" #include "refdb.h" +#include "regexp.h" #include "git2.h" @@ -42,7 +41,7 @@ static int maybe_abbrev(git_object** out, git_repository *repo, const char *spec return maybe_sha_or_abbrev(out, repo, spec, speclen); } -static int build_regex(regex_t *regex, const char *pattern) +static int build_regex(git_regexp *regex, const char *pattern) { int error; @@ -51,13 +50,11 @@ static int build_regex(regex_t *regex, const char *pattern) return GIT_EINVALIDSPEC; } - error = p_regcomp(regex, pattern, REG_EXTENDED); + error = git_regexp_compile(regex, pattern, 0); if (!error) return 0; - error = git_error_set_regex(regex, error); - - regfree(regex); + git_regexp_dispose(regex); return error; } @@ -66,7 +63,7 @@ static int maybe_describe(git_object**out, git_repository *repo, const char *spe { const char *substr; int error; - regex_t regex; + git_regexp regex; substr = strstr(spec, "-g"); @@ -76,8 +73,8 @@ static int maybe_describe(git_object**out, git_repository *repo, const char *spe if (build_regex(®ex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0) return -1; - error = regexec(®ex, spec, 0, NULL, 0); - regfree(®ex); + error = git_regexp_match(®ex, spec); + git_regexp_dispose(®ex); if (error) return GIT_ENOTFOUND; @@ -143,12 +140,11 @@ static int retrieve_previously_checked_out_branch_or_revision(git_object **out, { git_reference *ref = NULL; git_reflog *reflog = NULL; - regex_t preg; + git_regexp preg; int error = -1; size_t i, numentries, cur; const git_reflog_entry *entry; const char *msg; - regmatch_t regexmatches[2]; git_buf buf = GIT_BUF_INIT; cur = position; @@ -168,12 +164,14 @@ static int retrieve_previously_checked_out_branch_or_revision(git_object **out, numentries = git_reflog_entrycount(reflog); for (i = 0; i < numentries; i++) { + git_regmatch regexmatches[2]; + entry = git_reflog_entry_byindex(reflog, i); msg = git_reflog_entry_message(entry); if (!msg) continue; - if (regexec(&preg, msg, 2, regexmatches, 0)) + if (git_regexp_search(&preg, msg, 2, regexmatches) < 0) continue; cur--; @@ -181,7 +179,8 @@ static int retrieve_previously_checked_out_branch_or_revision(git_object **out, if (cur > 0) continue; - git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so); + if ((git_buf_put(&buf, msg+regexmatches[1].start, regexmatches[1].end - regexmatches[1].start)) < 0) + goto cleanup; if ((error = git_reference_dwim(base_ref, repo, git_buf_cstr(&buf))) == 0) goto cleanup; @@ -199,7 +198,7 @@ static int retrieve_previously_checked_out_branch_or_revision(git_object **out, cleanup: git_reference_free(ref); git_buf_dispose(&buf); - regfree(&preg); + git_regexp_dispose(&preg); git_reflog_free(reflog); return error; } @@ -448,7 +447,7 @@ cleanup: return error; } -static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex) +static int walk_and_search(git_object **out, git_revwalk *walk, git_regexp *regex) { int error; git_oid oid; @@ -460,7 +459,7 @@ static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex) if ((error < 0) && (error != GIT_ENOTFOUND)) return -1; - if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) { + if (!git_regexp_match(regex, git_commit_message((git_commit*)obj))) { *out = obj; return 0; } @@ -476,7 +475,7 @@ static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex) static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern) { - regex_t preg; + git_regexp preg; git_revwalk *walk = NULL; int error; @@ -497,7 +496,7 @@ static int handle_grep_syntax(git_object **out, git_repository *repo, const git_ error = walk_and_search(out, walk, &preg); cleanup: - regfree(&preg); + git_regexp_dispose(&preg); git_revwalk_free(walk); return error; diff --git a/src/revwalk.c b/src/revwalk.c index 1e3a5f2ff..abbd65ac2 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -21,13 +21,10 @@ git_commit_list_node *git_revwalk__commit_lookup( git_revwalk *walk, const git_oid *oid) { git_commit_list_node *commit; - size_t pos; - int ret; /* lookup and reserve space if not already present */ - pos = git_oidmap_lookup_index(walk->commits, oid); - if (git_oidmap_valid_index(walk->commits, pos)) - return git_oidmap_value_at(walk->commits, pos); + if ((commit = git_oidmap_get(walk->commits, oid)) != NULL) + return commit; commit = git_commit_list_alloc_node(walk); if (commit == NULL) @@ -35,14 +32,13 @@ git_commit_list_node *git_revwalk__commit_lookup( git_oid_cpy(&commit->oid, oid); - pos = git_oidmap_put(walk->commits, &commit->oid, &ret); - assert(ret != 0); - git_oidmap_set_value_at(walk->commits, pos, commit); + if ((git_oidmap_set(walk->commits, &commit->oid, commit)) < 0) + return NULL; return commit; } -static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting, int from_glob) +int git_revwalk__push_commit(git_revwalk *walk, const git_oid *oid, const git_revwalk__push_options *opts) { git_oid commit_id; int error; @@ -58,11 +54,11 @@ static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting, if (error == GIT_ENOTFOUND || error == GIT_EINVALIDSPEC || error == GIT_EPEEL) { /* If this comes from e.g. push_glob("tags"), ignore this */ - if (from_glob) + if (opts->from_glob) return 0; git_error_set(GIT_ERROR_INVALID, "object is not a committish"); - return -1; + return error; } if (error < 0) return error; @@ -78,16 +74,18 @@ static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting, if (commit->uninteresting) return 0; - if (uninteresting) { + if (opts->uninteresting) { walk->limited = 1; walk->did_hide = 1; } else { walk->did_push = 1; } - commit->uninteresting = uninteresting; + commit->uninteresting = opts->uninteresting; list = walk->user_input; - if (git_commit_list_insert(commit, &list) == NULL) { + if ((opts->insert_by_date && + git_commit_list_insert_by_date(commit, &list) == NULL) || + git_commit_list_insert(commit, &list) == NULL) { git_error_set_oom(); return -1; } @@ -99,29 +97,36 @@ static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting, int git_revwalk_push(git_revwalk *walk, const git_oid *oid) { + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; + assert(walk && oid); - return push_commit(walk, oid, 0, false); + + return git_revwalk__push_commit(walk, oid, &opts); } int git_revwalk_hide(git_revwalk *walk, const git_oid *oid) { + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; assert(walk && oid); - return push_commit(walk, oid, 1, false); + + opts.uninteresting = 1; + return git_revwalk__push_commit(walk, oid, &opts); } -static int push_ref(git_revwalk *walk, const char *refname, int hide, int from_glob) +int git_revwalk__push_ref(git_revwalk *walk, const char *refname, const git_revwalk__push_options *opts) { git_oid oid; if (git_reference_name_to_id(&oid, walk->repo, refname) < 0) return -1; - return push_commit(walk, &oid, hide, from_glob); + return git_revwalk__push_commit(walk, &oid, opts); } -static int push_glob(git_revwalk *walk, const char *glob, int hide) +int git_revwalk__push_glob(git_revwalk *walk, const char *glob, const git_revwalk__push_options *given_opts) { + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; int error = 0; git_buf buf = GIT_BUF_INIT; git_reference *ref; @@ -130,6 +135,9 @@ static int push_glob(git_revwalk *walk, const char *glob, int hide) assert(walk && glob); + if (given_opts) + memcpy(&opts, given_opts, sizeof(opts)); + /* refs/ is implied if not given in the glob */ if (git__prefixcmp(glob, GIT_REFS_DIR) != 0) git_buf_joinpath(&buf, GIT_REFS_DIR, glob); @@ -145,8 +153,9 @@ static int push_glob(git_revwalk *walk, const char *glob, int hide) if ((error = git_reference_iterator_glob_new(&iter, walk->repo, buf.ptr)) < 0) goto out; + opts.from_glob = true; while ((error = git_reference_next(&ref, iter)) == 0) { - error = push_ref(walk, git_reference_name(ref), hide, true); + error = git_revwalk__push_ref(walk, git_reference_name(ref), &opts); git_reference_free(ref); if (error < 0) break; @@ -162,52 +171,74 @@ out: int git_revwalk_push_glob(git_revwalk *walk, const char *glob) { + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; assert(walk && glob); - return push_glob(walk, glob, 0); + + return git_revwalk__push_glob(walk, glob, &opts); } int git_revwalk_hide_glob(git_revwalk *walk, const char *glob) { + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; assert(walk && glob); - return push_glob(walk, glob, 1); + + opts.uninteresting = 1; + return git_revwalk__push_glob(walk, glob, &opts); } int git_revwalk_push_head(git_revwalk *walk) { + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; assert(walk); - return push_ref(walk, GIT_HEAD_FILE, 0, false); + + return git_revwalk__push_ref(walk, GIT_HEAD_FILE, &opts); } int git_revwalk_hide_head(git_revwalk *walk) { + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; assert(walk); - return push_ref(walk, GIT_HEAD_FILE, 1, false); + + opts.uninteresting = 1; + return git_revwalk__push_ref(walk, GIT_HEAD_FILE, &opts); } int git_revwalk_push_ref(git_revwalk *walk, const char *refname) { + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; assert(walk && refname); - return push_ref(walk, refname, 0, false); + + return git_revwalk__push_ref(walk, refname, &opts); } int git_revwalk_push_range(git_revwalk *walk, const char *range) { + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; git_revspec revspec; int error = 0; if ((error = git_revparse(&revspec, walk->repo, range))) return error; + if (!revspec.to) { + git_error_set(GIT_ERROR_INVALID, "invalid revspec: range not provided"); + error = GIT_EINVALIDSPEC; + goto out; + } + if (revspec.flags & GIT_REVPARSE_MERGE_BASE) { /* TODO: support "..." */ git_error_set(GIT_ERROR_INVALID, "symmetric differences not implemented in revwalk"); - return GIT_EINVALIDSPEC; + error = GIT_EINVALIDSPEC; + goto out; } - if ((error = push_commit(walk, git_object_id(revspec.from), 1, false))) + opts.uninteresting = 1; + if ((error = git_revwalk__push_commit(walk, git_object_id(revspec.from), &opts))) goto out; - error = push_commit(walk, git_object_id(revspec.to), 0, false); + opts.uninteresting = 0; + error = git_revwalk__push_commit(walk, git_object_id(revspec.to), &opts); out: git_object_free(revspec.from); @@ -217,8 +248,10 @@ out: int git_revwalk_hide_ref(git_revwalk *walk, const char *refname) { + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; assert(walk && refname); - return push_ref(walk, refname, 1, false); + opts.uninteresting = 1; + return git_revwalk__push_ref(walk, refname, &opts); } static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit) @@ -626,8 +659,8 @@ int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) git_revwalk *walk = git__calloc(1, sizeof(git_revwalk)); GIT_ERROR_CHECK_ALLOC(walk); - walk->commits = git_oidmap_alloc(); - GIT_ERROR_CHECK_ALLOC(walk->commits); + if (git_oidmap_new(&walk->commits) < 0) + return -1; if (git_pqueue_init(&walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0) return -1; @@ -667,7 +700,7 @@ git_repository *git_revwalk_repository(git_revwalk *walk) return walk->repo; } -void git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode) +int git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode) { assert(walk); @@ -686,11 +719,14 @@ void git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode) if (walk->sorting != GIT_SORT_NONE) walk->limited = 1; + + return 0; } -void git_revwalk_simplify_first_parent(git_revwalk *walk) +int git_revwalk_simplify_first_parent(git_revwalk *walk) { walk->first_parent = 1; + return 0; } int git_revwalk_next(git_oid *oid, git_revwalk *walk) @@ -719,7 +755,7 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk) return error; } -void git_revwalk_reset(git_revwalk *walk) +int git_revwalk_reset(git_revwalk *walk) { git_commit_list_node *commit; @@ -744,6 +780,8 @@ void git_revwalk_reset(git_revwalk *walk) walk->limited = 0; walk->did_push = walk->did_hide = 0; walk->sorting = GIT_SORT_NONE; + + return 0; } int git_revwalk_add_hide_cb( diff --git a/src/revwalk.h b/src/revwalk.h index 923a2bc80..94b8a6fb1 100644 --- a/src/revwalk.h +++ b/src/revwalk.h @@ -50,4 +50,24 @@ struct git_revwalk { git_commit_list_node *git_revwalk__commit_lookup(git_revwalk *walk, const git_oid *oid); +typedef struct { + int uninteresting; + int from_glob; + int insert_by_date; +} git_revwalk__push_options; + +#define GIT_REVWALK__PUSH_OPTIONS_INIT { 0 } + +int git_revwalk__push_commit(git_revwalk *walk, + const git_oid *oid, + const git_revwalk__push_options *opts); + +int git_revwalk__push_ref(git_revwalk *walk, + const char *refname, + const git_revwalk__push_options *opts); + +int git_revwalk__push_glob(git_revwalk *walk, + const char *glob, + const git_revwalk__push_options *given_opts); + #endif diff --git a/src/settings.c b/src/settings.c index 95cbd89e4..f9f6b8497 100644 --- a/src/settings.c +++ b/src/settings.c @@ -25,14 +25,17 @@ #include "refs.h" #include "index.h" #include "transports/smart.h" +#include "transports/http.h" #include "streams/openssl.h" #include "streams/mbedtls.h" -void git_libgit2_version(int *major, int *minor, int *rev) +int git_libgit2_version(int *major, int *minor, int *rev) { *major = LIBGIT2_VER_MAJOR; *minor = LIBGIT2_VER_MINOR; *rev = LIBGIT2_VER_REVISION; + + return 0; } int git_libgit2_features(void) @@ -57,6 +60,7 @@ int git_libgit2_features(void) extern size_t git_mwindow__window_size; extern size_t git_mwindow__mapped_limit; extern size_t git_indexer__max_objects; +extern bool git_disable_pack_keep_file_checks; static int config_level_to_sysdir(int config_level) { @@ -279,6 +283,14 @@ int git_libgit2_opts(int key, ...) *(va_arg(ap, size_t *)) = git_indexer__max_objects; break; + case GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: + git_disable_pack_keep_file_checks = (va_arg(ap, int) != 0); + break; + + case GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE: + git_http__expect_continue = (va_arg(ap, int) != 0); + break; + default: git_error_set(GIT_ERROR_INVALID, "invalid option key"); error = -1; diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c deleted file mode 100644 index 14fcb40e5..000000000 --- a/src/sha1_lookup.c +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "sha1_lookup.h" - -#include - -#include "oid.h" - -int sha1_position(const void *table, - size_t stride, - unsigned lo, unsigned hi, - const unsigned char *key) -{ - const unsigned char *base = table; - - while (lo < hi) { - unsigned mi = (lo + hi) / 2; - int cmp = git_oid__hashcmp(base + mi * stride, key); - - if (!cmp) - return mi; - - if (cmp > 0) - hi = mi; - else - lo = mi+1; - } - - return -((int)lo)-1; -} diff --git a/src/sortedcache.c b/src/sortedcache.c index 02a685e10..8f7ea23e2 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -28,7 +28,7 @@ int git_sortedcache_new( git_pool_init(&sc->pool, 1); if (git_vector_init(&sc->items, 4, item_cmp) < 0 || - git_strmap_alloc(&sc->map) < 0) + git_strmap_new(&sc->map) < 0) goto fail; if (git_rwlock_init(&sc->lock)) { @@ -270,23 +270,19 @@ int git_sortedcache_clear(git_sortedcache *sc, bool wlock) /* find and/or insert item, returning pointer to item data */ int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) { - size_t pos; - int error = 0; - void *item; size_t keylen, itemlen; + int error = 0; char *item_key; + void *item; - pos = git_strmap_lookup_index(sc->map, key); - if (git_strmap_valid_index(sc->map, pos)) { - item = git_strmap_value_at(sc->map, pos); + if ((item = git_strmap_get(sc->map, key)) != NULL) goto done; - } keylen = strlen(key); itemlen = sc->item_path_offset + keylen + 1; itemlen = (itemlen + 7) & ~7; - if ((item = git_pool_mallocz(&sc->pool, (uint32_t)itemlen)) == NULL) { + if ((item = git_pool_mallocz(&sc->pool, itemlen)) == NULL) { /* don't use GIT_ERROR_CHECK_ALLOC b/c of lock */ error = -1; goto done; @@ -299,17 +295,11 @@ int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key) item_key = ((char *)item) + sc->item_path_offset; memcpy(item_key, key, keylen); - pos = git_strmap_put(sc->map, item_key, &error); - if (error < 0) + if ((error = git_strmap_set(sc->map, item_key, item)) < 0) goto done; - if (!error) - git_strmap_set_key_at(sc->map, pos, item_key); - git_strmap_set_value_at(sc->map, pos, item); - - error = git_vector_insert(&sc->items, item); - if (error < 0) - git_strmap_delete_at(sc->map, pos); + if ((error = git_vector_insert(&sc->items, item)) < 0) + git_strmap_delete(sc->map, item_key); done: if (out) @@ -320,10 +310,7 @@ done: /* lookup item by key */ void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key) { - size_t pos = git_strmap_lookup_index(sc->map, key); - if (git_strmap_valid_index(sc->map, pos)) - return git_strmap_value_at(sc->map, pos); - return NULL; + return git_strmap_get(sc->map, key); } /* find out how many items are in the cache */ @@ -371,9 +358,9 @@ int git_sortedcache_lookup_index( int git_sortedcache_remove(git_sortedcache *sc, size_t pos) { char *item; - size_t mappos; - /* because of pool allocation, this can't actually remove the item, + /* + * Because of pool allocation, this can't actually remove the item, * but we can remove it from the items vector and the hash table. */ @@ -384,8 +371,7 @@ int git_sortedcache_remove(git_sortedcache *sc, size_t pos) (void)git_vector_remove(&sc->items, pos); - mappos = git_strmap_lookup_index(sc->map, item + sc->item_path_offset); - git_strmap_delete_at(sc->map, mappos); + git_strmap_delete(sc->map, item + sc->item_path_offset); if (sc->free_item) sc->free_item(sc->free_item_payload, item); diff --git a/src/sortedcache.h b/src/sortedcache.h index a53ff48a3..e553d01dd 100644 --- a/src/sortedcache.h +++ b/src/sortedcache.h @@ -10,7 +10,7 @@ #include "common.h" #include "util.h" -#include "fileops.h" +#include "futils.h" #include "vector.h" #include "thread-utils.h" #include "pool.h" diff --git a/src/stash.c b/src/stash.c index c332d93bb..790f56fdd 100644 --- a/src/stash.c +++ b/src/stash.c @@ -173,7 +173,7 @@ static int stash_to_index( git_index *index, const char *path) { - git_index *repo_index; + git_index *repo_index = NULL; git_index_entry entry = {{0}}; struct stat st; int error; @@ -187,7 +187,7 @@ static int stash_to_index( return error; git_index_entry__init_from_stat(&entry, &st, - (repo_index != NULL || !repo_index->distrust_filemode)); + (repo_index == NULL || !repo_index->distrust_filemode)); entry.path = path; @@ -398,28 +398,23 @@ static int commit_worktree( git_commit *b_commit, git_commit *u_commit) { - int error = 0; - git_tree *w_tree = NULL, *i_tree = NULL; - git_index *i_index = NULL; - const git_commit *parents[] = { NULL, NULL, NULL }; - int ignorecase; + const git_commit *parents[] = { NULL, NULL, NULL }; + git_index *i_index = NULL, *r_index = NULL; + git_tree *w_tree = NULL; + int error = 0, ignorecase; parents[0] = b_commit; parents[1] = i_commit; parents[2] = u_commit; - if ((error = git_commit_tree(&i_tree, i_commit)) < 0) - goto cleanup; - - if ((error = git_index_new(&i_index)) < 0 || - (error = git_repository__cvar(&ignorecase, repo, GIT_CVAR_IGNORECASE)) < 0) + if ((error = git_repository_index(&r_index, repo) < 0) || + (error = git_index_new(&i_index)) < 0 || + (error = git_index__fill(i_index, &r_index->entries) < 0) || + (error = git_repository__configmap_lookup(&ignorecase, repo, GIT_CONFIGMAP_IGNORECASE)) < 0) goto cleanup; git_index__set_ignore_case(i_index, ignorecase); - if ((error = git_index_read_tree(i_index, i_tree)) < 0) - goto cleanup; - if ((error = build_workdir_tree(&w_tree, repo, i_index, b_commit)) < 0) goto cleanup; @@ -436,42 +431,39 @@ static int commit_worktree( parents); cleanup: - git_tree_free(i_tree); git_tree_free(w_tree); git_index_free(i_index); + git_index_free(r_index); return error; } -static int prepare_worktree_commit_message( - git_buf* msg, - const char *user_message) +static int prepare_worktree_commit_message(git_buf *out, const char *user_message) { git_buf buf = GIT_BUF_INIT; - int error; + int error = 0; - if ((error = git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg))) < 0) - return error; - - git_buf_clear(msg); - - if (!user_message) - git_buf_printf(msg, "WIP on %s", git_buf_cstr(&buf)); - else { + if (!user_message) { + git_buf_printf(&buf, "WIP on %s", git_buf_cstr(out)); + } else { const char *colon; - if ((colon = strchr(git_buf_cstr(&buf), ':')) == NULL) + if ((colon = strchr(git_buf_cstr(out), ':')) == NULL) goto cleanup; - git_buf_puts(msg, "On "); - git_buf_put(msg, git_buf_cstr(&buf), colon - buf.ptr); - git_buf_printf(msg, ": %s\n", user_message); + git_buf_puts(&buf, "On "); + git_buf_put(&buf, git_buf_cstr(out), colon - out->ptr); + git_buf_printf(&buf, ": %s\n", user_message); } - error = (git_buf_oom(msg) || git_buf_oom(&buf)) ? -1 : 0; + if (git_buf_oom(&buf)) { + error = -1; + goto cleanup; + } + + git_buf_swap(out, &buf); cleanup: git_buf_dispose(&buf); - return error; } @@ -502,10 +494,7 @@ static int is_dirty_cb(const char *path, unsigned int status, void *payload) return GIT_PASSTHROUGH; } -static int ensure_there_are_changes_to_stash( - git_repository *repo, - bool include_untracked_files, - bool include_ignored_files) +static int ensure_there_are_changes_to_stash(git_repository *repo, uint32_t flags) { int error; git_status_options opts = GIT_STATUS_OPTIONS_INIT; @@ -513,11 +502,11 @@ static int ensure_there_are_changes_to_stash( opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES; - if (include_untracked_files) + if (flags & GIT_STASH_INCLUDE_UNTRACKED) opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - if (include_ignored_files) + if (flags & GIT_STASH_INCLUDE_IGNORED) opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; @@ -532,20 +521,14 @@ static int ensure_there_are_changes_to_stash( return error; } -static int reset_index_and_workdir( - git_repository *repo, - git_commit *commit, - bool remove_untracked, - bool remove_ignored) +static int reset_index_and_workdir(git_repository *repo, git_commit *commit, uint32_t flags) { git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; opts.checkout_strategy = GIT_CHECKOUT_FORCE; - - if (remove_untracked) + if (flags & GIT_STASH_INCLUDE_UNTRACKED) opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED; - - if (remove_ignored) + if (flags & GIT_STASH_INCLUDE_IGNORED) opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_IGNORED; return git_checkout_tree(repo, (git_object *)commit, &opts); @@ -571,31 +554,26 @@ int git_stash_save( if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0) goto cleanup; - if ((error = ensure_there_are_changes_to_stash( - repo, - (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0, - (flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0) + if ((error = ensure_there_are_changes_to_stash(repo, flags)) < 0) goto cleanup; if ((error = git_repository_index(&index, repo)) < 0) goto cleanup; - if ((error = commit_index( - &i_commit, repo, index, stasher, git_buf_cstr(&msg), b_commit)) < 0) + if ((error = commit_index(&i_commit, repo, index, stasher, + git_buf_cstr(&msg), b_commit)) < 0) goto cleanup; if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) && - (error = commit_untracked( - &u_commit, repo, stasher, git_buf_cstr(&msg), - i_commit, flags)) < 0) + (error = commit_untracked(&u_commit, repo, stasher, + git_buf_cstr(&msg), i_commit, flags)) < 0) goto cleanup; if ((error = prepare_worktree_commit_message(&msg, message)) < 0) goto cleanup; - if ((error = commit_worktree( - out, repo, stasher, git_buf_cstr(&msg), - i_commit, b_commit, u_commit)) < 0) + if ((error = commit_worktree(out, repo, stasher, git_buf_cstr(&msg), + i_commit, b_commit, u_commit)) < 0) goto cleanup; git_buf_rtrim(&msg); @@ -603,11 +581,8 @@ int git_stash_save( if ((error = update_reflog(out, repo, git_buf_cstr(&msg))) < 0) goto cleanup; - if ((error = reset_index_and_workdir( - repo, - ((flags & GIT_STASH_KEEP_INDEX) != 0) ? i_commit : b_commit, - (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0, - (flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0) + if ((error = reset_index_and_workdir(repo, (flags & GIT_STASH_KEEP_INDEX) ? i_commit : b_commit, + flags)) < 0) goto cleanup; cleanup: @@ -794,13 +769,18 @@ static void normalize_apply_options( opts->checkout_options.their_label = "Stashed changes"; } -int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version) +int git_stash_apply_options_init(git_stash_apply_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_stash_apply_options, GIT_STASH_APPLY_OPTIONS_INIT); return 0; } +int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version) +{ + return git_stash_apply_options_init(opts, version); +} + #define NOTIFY_PROGRESS(opts, progress_type) \ do { \ if ((opts).progress_cb && \ diff --git a/src/status.c b/src/status.c index ef32a0a8e..6a1284415 100644 --- a/src/status.c +++ b/src/status.c @@ -8,7 +8,7 @@ #include "status.h" #include "git2.h" -#include "fileops.h" +#include "futils.h" #include "hash.h" #include "vector.h" #include "tree.h" @@ -16,6 +16,7 @@ #include "repository.h" #include "ignore.h" #include "index.h" +#include "wildmatch.h" #include "git2/diff.h" #include "diff.h" @@ -85,15 +86,15 @@ static unsigned int workdir_delta2status( /* if OIDs don't match, we might need to calculate them now to * discern between RENAMED vs RENAMED+MODIFED */ - if (git_oid_iszero(&idx2wd->old_file.id) && - diff->old_src == GIT_ITERATOR_TYPE_WORKDIR && + if (git_oid_is_zero(&idx2wd->old_file.id) && + diff->old_src == GIT_ITERATOR_WORKDIR && !git_diff__oid_for_file( &idx2wd->old_file.id, diff, idx2wd->old_file.path, idx2wd->old_file.mode, idx2wd->old_file.size)) idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - if (git_oid_iszero(&idx2wd->new_file.id) && - diff->new_src == GIT_ITERATOR_TYPE_WORKDIR && + if (git_oid_is_zero(&idx2wd->new_file.id) && + diff->new_src == GIT_ITERATOR_WORKDIR && !git_diff__oid_for_file( &idx2wd->new_file.id, diff, idx2wd->new_file.path, idx2wd->new_file.mode, idx2wd->new_file.size)) @@ -280,7 +281,7 @@ int git_status_list_new( if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 || (error = git_repository_index(&index, repo)) < 0) return error; - + if (opts != NULL && opts->baseline != NULL) { head = opts->baseline; } else { @@ -456,7 +457,7 @@ struct status_file_info { char *expected; unsigned int count; unsigned int status; - int fnm_flags; + int wildmatch_flags; int ambiguous; }; @@ -468,11 +469,11 @@ static int get_one_status(const char *path, unsigned int status, void *data) sfi->count++; sfi->status = status; - strcomp = (sfi->fnm_flags & FNM_CASEFOLD) ? git__strcasecmp : git__strcmp; + strcomp = (sfi->wildmatch_flags & WM_CASEFOLD) ? git__strcasecmp : git__strcmp; if (sfi->count > 1 || (strcomp(sfi->expected, path) != 0 && - p_fnmatch(sfi->expected, path, sfi->fnm_flags) != 0)) + wildmatch(sfi->expected, path, sfi->wildmatch_flags) != 0)) { sfi->ambiguous = true; return GIT_EAMBIGUOUS; /* git_error_set will be done by caller */ @@ -499,7 +500,7 @@ int git_status_file( if ((sfi.expected = git__strdup(path)) == NULL) return -1; if (index->ignore_case) - sfi.fnm_flags = FNM_CASEFOLD; + sfi.wildmatch_flags = WM_CASEFOLD; opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | @@ -540,13 +541,18 @@ int git_status_should_ignore( return git_ignore_path_is_ignored(ignored, repo, path); } -int git_status_init_options(git_status_options *opts, unsigned int version) +int git_status_options_init(git_status_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_status_options, GIT_STATUS_OPTIONS_INIT); return 0; } +int git_status_init_options(git_status_options *opts, unsigned int version) +{ + return git_status_options_init(opts, version); +} + int git_status_list_get_perfdata( git_diff_perfdata *out, const git_status_list *status) { diff --git a/src/streams/openssl.c b/src/streams/openssl.c index fe5f79cce..5b66352ca 100644 --- a/src/streams/openssl.c +++ b/src/streams/openssl.c @@ -156,7 +156,7 @@ static void openssl_locking_function( lock = mode & CRYPTO_LOCK; if (lock) { - git_mutex_lock(&openssl_locks[n]); + (void)git_mutex_lock(&openssl_locks[n]); } else { git_mutex_unlock(&openssl_locks[n]); } @@ -196,16 +196,69 @@ static void shutdown_ssl(void) } } +#ifdef VALGRIND +#ifdef OPENSSL_LEGACY_API +static void *git_openssl_malloc(size_t bytes) +{ + return git__calloc(1, bytes); +} + +static void *git_openssl_realloc(void *mem, size_t size) +{ + return git__realloc(mem, size); +} + +static void git_openssl_free(void *mem) +{ + return git__free(mem); +} +#else +static void *git_openssl_malloc(size_t bytes, const char *file, int line) +{ + GIT_UNUSED(file); + GIT_UNUSED(line); + return git__calloc(1, bytes); +} + +static void *git_openssl_realloc(void *mem, size_t size, const char *file, int line) +{ + GIT_UNUSED(file); + GIT_UNUSED(line); + return git__realloc(mem, size); +} + +static void git_openssl_free(void *mem, const char *file, int line) +{ + GIT_UNUSED(file); + GIT_UNUSED(line); + return git__free(mem); +} +#endif +#endif + int git_openssl_stream_global_init(void) { long ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; const char *ciphers = git_libgit2__ssl_ciphers(); +#ifdef VALGRIND + static bool allocators_initialized = false; +#endif /* Older OpenSSL and MacOS OpenSSL doesn't have this */ #ifdef SSL_OP_NO_COMPRESSION ssl_opts |= SSL_OP_NO_COMPRESSION; #endif +#ifdef VALGRIND + /* Swap in our own allocator functions that initialize allocated memory */ + if (!allocators_initialized && + CRYPTO_set_mem_functions(git_openssl_malloc, + git_openssl_realloc, + git_openssl_free) != 1) + goto error; + allocators_initialized = true; +#endif + OPENSSL_init_ssl(0, NULL); /* @@ -310,7 +363,6 @@ static int bio_read(BIO *b, char *buf, int len) static int bio_write(BIO *b, const char *buf, int len) { git_stream *io = (git_stream *) BIO_get_data(b); - return (int) git_stream_write(io, buf, len, 0); } diff --git a/src/streams/socket.c b/src/streams/socket.c index 066580f64..33f7883cd 100644 --- a/src/streams/socket.c +++ b/src/streams/socket.c @@ -138,7 +138,7 @@ static ssize_t socket_write(git_stream *stream, const char *data, size_t len, in errno = 0; if ((written = p_send(st->s, data, len, flags)) < 0) { - net_set_error("Error sending data"); + net_set_error("error sending data"); return -1; } @@ -151,7 +151,7 @@ static ssize_t socket_read(git_stream *stream, void *data, size_t len) git_socket_stream *st = (git_socket_stream *) stream; if ((ret = p_recv(st->s, data, len, 0)) < 0) - net_set_error("Error receiving socket data"); + net_set_error("error receiving socket data"); return ret; } diff --git a/src/strmap.c b/src/strmap.c index 20b14acf3..c6e5b6dc7 100644 --- a/src/strmap.c +++ b/src/strmap.c @@ -18,12 +18,10 @@ __KHASH_TYPE(str, const char *, void *) __KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) -int git_strmap_alloc(git_strmap **map) +int git_strmap_new(git_strmap **out) { - if ((*map = kh_init(str)) == NULL) { - git_error_set_oom(); - return -1; - } + *out = kh_init(str); + GIT_ERROR_CHECK_ALLOC(*out); return 0; } @@ -38,19 +36,43 @@ void git_strmap_clear(git_strmap *map) kh_clear(str, map); } -size_t git_strmap_num_entries(git_strmap *map) +size_t git_strmap_size(git_strmap *map) { return kh_size(map); } -size_t git_strmap_lookup_index(git_strmap *map, const char *key) +void *git_strmap_get(git_strmap *map, const char *key) { - return kh_get(str, map, key); + size_t idx = kh_get(str, map, key); + if (idx == kh_end(map) || !kh_exist(map, idx)) + return NULL; + return kh_val(map, idx); } -int git_strmap_valid_index(git_strmap *map, size_t idx) +int git_strmap_set(git_strmap *map, const char *key, void *value) { - return idx != kh_end(map); + size_t idx; + int rval; + + idx = kh_put(str, map, key, &rval); + if (rval < 0) + return -1; + + if (rval == 0) + kh_key(map, idx) = key; + + kh_val(map, idx) = value; + + return 0; +} + +int git_strmap_delete(git_strmap *map, const char *key) +{ + khiter_t idx = kh_get(str, map, key); + if (idx == kh_end(map)) + return GIT_ENOTFOUND; + kh_del(str, map, idx); + return 0; } int git_strmap_exists(git_strmap *map, const char *key) @@ -58,90 +80,21 @@ int git_strmap_exists(git_strmap *map, const char *key) return kh_get(str, map, key) != kh_end(map); } -int git_strmap_has_data(git_strmap *map, size_t idx) +int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key) { - return kh_exist(map, idx); -} + size_t i = *iter; -const char *git_strmap_key(git_strmap *map, size_t idx) -{ - return kh_key(map, idx); -} + while (i < map->n_buckets && !kh_exist(map, i)) + i++; -void git_strmap_set_key_at(git_strmap *map, size_t idx, char *key) -{ - kh_val(map, idx) = key; -} + if (i >= map->n_buckets) + return GIT_ITEROVER; -void *git_strmap_value_at(git_strmap *map, size_t idx) -{ - return kh_val(map, idx); -} + if (key) + *key = kh_key(map, i); + if (value) + *value = kh_val(map, i); + *iter = ++i; -void git_strmap_set_value_at(git_strmap *map, size_t idx, void *value) -{ - kh_val(map, idx) = value; -} - -void git_strmap_delete_at(git_strmap *map, size_t idx) -{ - kh_del(str, map, idx); -} - -int git_strmap_put(git_strmap *map, const char *key, int *err) -{ - return kh_put(str, map, key, err); -} - -void git_strmap_insert(git_strmap *map, const char *key, void *value, int *rval) -{ - khiter_t idx = kh_put(str, map, key, rval); - - if ((*rval) >= 0) { - if ((*rval) == 0) - kh_key(map, idx) = key; - kh_val(map, idx) = value; - } -} - -void git_strmap_delete(git_strmap *map, const char *key) -{ - khiter_t idx = git_strmap_lookup_index(map, key); - if (git_strmap_valid_index(map, idx)) - git_strmap_delete_at(map, idx); -} - -size_t git_strmap_begin(git_strmap *map) -{ - GIT_UNUSED(map); return 0; } - -size_t git_strmap_end(git_strmap *map) -{ - return map->n_buckets; -} - -int git_strmap_next( - void **data, - size_t* iter, - git_strmap *map) -{ - if (!map) - return GIT_ERROR; - - while (*iter != git_strmap_end(map)) { - if (!(git_strmap_has_data(map, *iter))) { - ++(*iter); - continue; - } - - *data = git_strmap_value_at(map, *iter); - - ++(*iter); - - return GIT_OK; - } - - return GIT_ITEROVER; -} diff --git a/src/strmap.h b/src/strmap.h index 2649acbfb..9f5e4cc8b 100644 --- a/src/strmap.h +++ b/src/strmap.h @@ -9,51 +9,123 @@ #include "common.h" +/** A map with C strings as key. */ typedef struct kh_str_s git_strmap; -int git_strmap_alloc(git_strmap **map); +/** + * Allocate a new string map. + * + * @param out Pointer to the map that shall be allocated. + * @return 0 on success, an error code if allocation has failed. + */ +int git_strmap_new(git_strmap **out); + +/** + * Free memory associated with the map. + * + * Note that this function will _not_ free keys or values added + * to this map. + * + * @param map Pointer to the map that is to be free'd. May be + * `NULL`. + */ void git_strmap_free(git_strmap *map); + +/** + * Clear all entries from the map. + * + * This function will remove all entries from the associated map. + * Memory associated with it will not be released, though. + * + * @param map Pointer to the map that shall be cleared. May be + * `NULL`. + */ void git_strmap_clear(git_strmap *map); -size_t git_strmap_num_entries(git_strmap *map); +/** + * Return the number of elements in the map. + * + * @parameter map map containing the elements + * @return number of elements in the map + */ +size_t git_strmap_size(git_strmap *map); -size_t git_strmap_lookup_index(git_strmap *map, const char *key); -int git_strmap_valid_index(git_strmap *map, size_t idx); +/** + * Return value associated with the given key. + * + * @param map map to search key in + * @param key key to search for + * @return value associated with the given key or NULL if the key was not found + */ +void *git_strmap_get(git_strmap *map, const char *key); +/** + * Set the entry for key to value. + * + * If the map has no corresponding entry for the given key, a new + * entry will be created with the given value. If an entry exists + * already, its value will be updated to match the given value. + * + * @param map map to create new entry in + * @param key key to set + * @param value value to associate the key with; may be NULL + * @return zero if the key was successfully set, a negative error + * code otherwise + */ +int git_strmap_set(git_strmap *map, const char *key, void *value); + +/** + * Delete an entry from the map. + * + * Delete the given key and its value from the map. If no such + * key exists, this will do nothing. + * + * @param map map to delete key in + * @param key key to delete + * @return `0` if the key has been deleted, GIT_ENOTFOUND if no + * such key was found, a negative code in case of an + * error + */ +int git_strmap_delete(git_strmap *map, const char *key); + +/** + * Check whether a key exists in the given map. + * + * @param map map to query for the key + * @param key key to search for + * @return 0 if the key has not been found, 1 otherwise + */ int git_strmap_exists(git_strmap *map, const char *key); -int git_strmap_has_data(git_strmap *map, size_t idx); -const char *git_strmap_key(git_strmap *map, size_t idx); -void git_strmap_set_key_at(git_strmap *map, size_t idx, char *key); -void *git_strmap_value_at(git_strmap *map, size_t idx); -void git_strmap_set_value_at(git_strmap *map, size_t idx, void *value); -void git_strmap_delete_at(git_strmap *map, size_t idx); +/** + * Iterate over entries of the map. + * + * This functions allows to iterate over all key-value entries of + * the map. The current position is stored in the `iter` variable + * and should be initialized to `0` before the first call to this + * function. + * + * @param map map to iterate over + * @param value pointer to the variable where to store the current + * value. May be NULL. + * @param iter iterator storing the current position. Initialize + * with zero previous to the first call. + * @param key pointer to the variable where to store the current + * key. May be NULL. + * @return `0` if the next entry was correctly retrieved. + * GIT_ITEROVER if no entries are left. A negative error + * code otherwise. + */ +int git_strmap_iterate(void **value, git_strmap *map, size_t *iter, const char **key); -int git_strmap_put(git_strmap *map, const char *key, int *err); -void git_strmap_insert(git_strmap *map, const char *key, void *value, int *rval); -void git_strmap_delete(git_strmap *map, const char *key); - -#define git_strmap_foreach(h, kvar, vvar, code) { size_t __i; \ - for (__i = git_strmap_begin(h); __i != git_strmap_end(h); ++__i) { \ - if (!git_strmap_has_data(h,__i)) continue; \ - (kvar) = git_strmap_key(h,__i); \ - (vvar) = git_strmap_value_at(h,__i); \ +#define git_strmap_foreach(h, kvar, vvar, code) { size_t __i = 0; \ + while (git_strmap_iterate((void **) &(vvar), h, &__i, &(kvar)) == 0) { \ code; \ } } -#define git_strmap_foreach_value(h, vvar, code) { size_t __i; \ - for (__i = git_strmap_begin(h); __i != git_strmap_end(h); ++__i) { \ - if (!git_strmap_has_data(h,__i)) continue; \ - (vvar) = git_strmap_value_at(h,__i); \ +#define git_strmap_foreach_value(h, vvar, code) { size_t __i = 0; \ + while (git_strmap_iterate((void **) &(vvar), h, &__i, NULL) == 0) { \ code; \ } } -size_t git_strmap_begin(git_strmap *map); -size_t git_strmap_end(git_strmap *map); - -int git_strmap_next( - void **data, - size_t *iter, - git_strmap *map); - #endif diff --git a/src/submodule.c b/src/submodule.c index e3ec88554..1690e08f8 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -23,31 +23,32 @@ #include "path.h" #include "index.h" #include "worktree.h" +#include "clone.h" #define GIT_MODULES_FILE ".gitmodules" -static git_cvar_map _sm_update_map[] = { - {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT}, - {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, - {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}, - {GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE}, - {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_UPDATE_NONE}, - {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_UPDATE_CHECKOUT}, +static git_configmap _sm_update_map[] = { + {GIT_CONFIGMAP_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT}, + {GIT_CONFIGMAP_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, + {GIT_CONFIGMAP_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}, + {GIT_CONFIGMAP_STRING, "none", GIT_SUBMODULE_UPDATE_NONE}, + {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_UPDATE_NONE}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_UPDATE_CHECKOUT}, }; -static git_cvar_map _sm_ignore_map[] = { - {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}, - {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, - {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, - {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, - {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_IGNORE_NONE}, - {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_IGNORE_ALL}, +static git_configmap _sm_ignore_map[] = { + {GIT_CONFIGMAP_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}, + {GIT_CONFIGMAP_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, + {GIT_CONFIGMAP_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, + {GIT_CONFIGMAP_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, + {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_IGNORE_NONE}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_IGNORE_ALL}, }; -static git_cvar_map _sm_recurse_map[] = { - {GIT_CVAR_STRING, "on-demand", GIT_SUBMODULE_RECURSE_ONDEMAND}, - {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_RECURSE_NO}, - {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_RECURSE_YES}, +static git_configmap _sm_recurse_map[] = { + {GIT_CONFIGMAP_STRING, "on-demand", GIT_SUBMODULE_RECURSE_ONDEMAND}, + {GIT_CONFIGMAP_FALSE, NULL, GIT_SUBMODULE_RECURSE_NO}, + {GIT_CONFIGMAP_TRUE, NULL, GIT_SUBMODULE_RECURSE_YES}, }; enum { @@ -197,12 +198,11 @@ static int load_submodule_names(git_strmap **out, git_repository *repo, git_conf git_config_entry *entry; git_buf buf = GIT_BUF_INIT; git_strmap *names; - int rval, isvalid; - int error = 0; + int isvalid, error; *out = NULL; - if ((error = git_strmap_alloc(&names)) < 0) + if ((error = git_strmap_new(&names)) < 0) goto out; if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0) @@ -230,8 +230,7 @@ static int load_submodule_names(git_strmap **out, git_repository *repo, git_conf if (!isvalid) continue; - git_strmap_insert(names, git__strdup(entry->value), git_buf_detach(&buf), &rval); - if (rval < 0) { + if ((error = git_strmap_set(names, git__strdup(entry->value), git_buf_detach(&buf))) < 0) { git_error_set(GIT_ERROR_NOMEMORY, "error inserting submodule into hash table"); error = -1; goto out; @@ -267,10 +266,9 @@ int git_submodule_lookup( } if (repo->submodule_cache != NULL) { - size_t pos = git_strmap_lookup_index(repo->submodule_cache, name); - if (git_strmap_valid_index(repo->submodule_cache, pos)) { + if ((sm = git_strmap_get(repo->submodule_cache, name)) != NULL) { if (out) { - *out = git_strmap_value_at(repo->submodule_cache, pos); + *out = sm; GIT_REFCOUNT_INC(*out); } return 0; @@ -395,30 +393,21 @@ static void submodule_free_dup(void *sm) static int submodule_get_or_create(git_submodule **out, git_repository *repo, git_strmap *map, const char *name) { - int error = 0; - size_t pos; git_submodule *sm = NULL; + int error; - pos = git_strmap_lookup_index(map, name); - if (git_strmap_valid_index(map, pos)) { - sm = git_strmap_value_at(map, pos); + if ((sm = git_strmap_get(map, name)) != NULL) goto done; - } /* if the submodule doesn't exist yet in the map, create it */ if ((error = submodule_alloc(&sm, repo, name)) < 0) return error; - pos = git_strmap_put(map, sm->name, &error); - /* nobody can beat us to adding it */ - assert(error != 0); - if (error < 0) { + if ((error = git_strmap_set(map, sm->name, sm)) < 0) { git_submodule_free(sm); return error; } - git_strmap_set_value_at(map, pos, sm); - done: GIT_REFCOUNT_INC(sm); *out = sm; @@ -439,26 +428,18 @@ static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cf goto done; while (!(error = git_iterator_advance(&entry, i))) { - size_t pos = git_strmap_lookup_index(map, entry->path); git_submodule *sm; - if (git_strmap_valid_index(map, pos)) { - sm = git_strmap_value_at(map, pos); - + if ((sm = git_strmap_get(map, entry->path)) != NULL) { if (S_ISGITLINK(entry->mode)) submodule_update_from_index_entry(sm, entry); else sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; } else if (S_ISGITLINK(entry->mode)) { - size_t name_pos; const char *name; - name_pos = git_strmap_lookup_index(names, entry->path); - if (git_strmap_valid_index(names, name_pos)) { - name = git_strmap_value_at(names, name_pos); - } else { + if ((name = git_strmap_get(names, entry->path)) == NULL) name = entry->path; - } if (!submodule_get_or_create(&sm, git_index_owner(idx), map, name)) { submodule_update_from_index_entry(sm, entry); @@ -491,26 +472,18 @@ static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg goto done; while (!(error = git_iterator_advance(&entry, i))) { - size_t pos = git_strmap_lookup_index(map, entry->path); git_submodule *sm; - if (git_strmap_valid_index(map, pos)) { - sm = git_strmap_value_at(map, pos); - + if ((sm = git_strmap_get(map, entry->path)) != NULL) { if (S_ISGITLINK(entry->mode)) submodule_update_from_head_data(sm, entry->mode, &entry->id); else sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; } else if (S_ISGITLINK(entry->mode)) { - size_t name_pos; const char *name; - name_pos = git_strmap_lookup_index(names, entry->path); - if (git_strmap_valid_index(names, name_pos)) { - name = git_strmap_value_at(names, name_pos); - } else { + if ((name = git_strmap_get(names, entry->path)) == NULL) name = entry->path; - } if (!submodule_get_or_create(&sm, git_tree_owner(head), map, name)) { submodule_update_from_head_data( @@ -618,14 +591,14 @@ int git_submodule_foreach( return -1; } - if ((error = git_strmap_alloc(&submodules)) < 0) + if ((error = git_strmap_new(&submodules)) < 0) return error; if ((error = git_submodule__map(repo, submodules)) < 0) goto done; if (!(error = git_vector_init( - &snapshot, git_strmap_num_entries(submodules), submodule_cmp))) { + &snapshot, git_strmap_size(submodules), submodule_cmp))) { git_strmap_foreach_value(submodules, sm, { if ((error = git_vector_insert(&snapshot, sm)) < 0) @@ -843,6 +816,64 @@ done: return error; } +static int clone_return_origin(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload) +{ + GIT_UNUSED(url); + GIT_UNUSED(payload); + return git_remote_lookup(out, repo, name); +} + +static int clone_return_repo(git_repository **out, const char *path, int bare, void *payload) +{ + git_submodule *sm = payload; + + GIT_UNUSED(path); + GIT_UNUSED(bare); + return git_submodule_open(out, sm); +} + +int git_submodule_clone(git_repository **out, git_submodule *submodule, const git_submodule_update_options *given_opts) +{ + int error; + git_repository *clone; + git_buf rel_path = GIT_BUF_INIT; + git_submodule_update_options sub_opts = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + + assert(submodule); + + if (given_opts) + memcpy(&sub_opts, given_opts, sizeof(sub_opts)); + + GIT_ERROR_CHECK_VERSION(&sub_opts, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options"); + + memcpy(&opts.checkout_opts, &sub_opts.checkout_opts, sizeof(sub_opts.checkout_opts)); + memcpy(&opts.fetch_opts, &sub_opts.fetch_opts, sizeof(sub_opts.fetch_opts)); + opts.repository_cb = clone_return_repo; + opts.repository_cb_payload = submodule; + opts.remote_cb = clone_return_origin; + opts.remote_cb_payload = submodule; + + git_buf_puts(&rel_path, git_repository_workdir(git_submodule_owner(submodule))); + git_buf_joinpath(&rel_path, git_buf_cstr(&rel_path), git_submodule_path(submodule)); + + GIT_ERROR_CHECK_ALLOC_BUF(&rel_path); + + error = git_clone__submodule(&clone, git_submodule_url(submodule), git_buf_cstr(&rel_path), &opts); + if (error < 0) + goto cleanup; + + if (!out) + git_repository_free(clone); + else + *out = clone; + +cleanup: + git_buf_dispose(&rel_path); + + return error; +} + int git_submodule_add_finalize(git_submodule *sm) { int error; @@ -1017,9 +1048,9 @@ cleanup: return error; } -static int write_mapped_var(git_repository *repo, const char *name, git_cvar_map *maps, size_t nmaps, const char *var, int ival) +static int write_mapped_var(git_repository *repo, const char *name, git_configmap *maps, size_t nmaps, const char *var, int ival) { - git_cvar_t type; + git_configmap_t type; const char *val; if (git_config_lookup_map_enum(&type, &val, maps, nmaps, ival) < 0) { @@ -1027,7 +1058,7 @@ static int write_mapped_var(git_repository *repo, const char *name, git_cvar_map return -1; } - if (type == GIT_CVAR_TRUE) + if (type == GIT_CONFIGMAP_TRUE) val = "true"; return write_var(repo, name, var, val); @@ -1202,13 +1233,18 @@ static int git_submodule_update_repo_init_cb( return submodule_repo_create(out, sm->repo, path); } -int git_submodule_update_init_options(git_submodule_update_options *opts, unsigned int version) +int git_submodule_update_options_init(git_submodule_update_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE( opts, version, git_submodule_update_options, GIT_SUBMODULE_UPDATE_OPTIONS_INIT); return 0; } +int git_submodule_update_init_options(git_submodule_update_options *opts, unsigned int version) +{ + return git_submodule_update_options_init(opts, version); +} + int git_submodule_update(git_submodule *sm, int init, git_submodule_update_options *_update_options) { int error; @@ -1384,50 +1420,46 @@ cleanup: int git_submodule_sync(git_submodule *sm) { - int error = 0; - git_config *cfg = NULL; - git_buf key = GIT_BUF_INIT; + git_buf key = GIT_BUF_INIT, url = GIT_BUF_INIT, remote_name = GIT_BUF_INIT; git_repository *smrepo = NULL; + git_config *cfg = NULL; + int error = 0; if (!sm->url) { - git_error_set(GIT_ERROR_SUBMODULE, - "no URL configured for submodule '%s'", sm->name); + git_error_set(GIT_ERROR_SUBMODULE, "no URL configured for submodule '%s'", sm->name); return -1; } /* copy URL over to config only if it already exists */ + if ((error = git_repository_config__weakptr(&cfg, sm->repo)) < 0 || + (error = git_buf_printf(&key, "submodule.%s.url", sm->name)) < 0 || + (error = git_submodule_resolve_url(&url, sm->repo, sm->url)) < 0 || + (error = git_config__update_entry(cfg, key.ptr, url.ptr, true, true)) < 0) + goto out; - if (!(error = git_repository_config__weakptr(&cfg, sm->repo)) && - !(error = git_buf_printf(&key, "submodule.%s.url", sm->name))) - error = git_config__update_entry(cfg, key.ptr, sm->url, true, true); + if (!(sm->flags & GIT_SUBMODULE_STATUS_IN_WD)) + goto out; /* if submodule exists in the working directory, update remote url */ + if ((error = git_submodule_open(&smrepo, sm)) < 0 || + (error = git_repository_config__weakptr(&cfg, smrepo)) < 0) + goto out; - if (!error && - (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0 && - !(error = git_submodule_open(&smrepo, sm))) - { - git_buf remote_name = GIT_BUF_INIT; - - if ((error = git_repository_config__weakptr(&cfg, smrepo)) < 0) - /* return error from reading submodule config */; - else if ((error = lookup_head_remote_key(&remote_name, smrepo)) < 0) { - git_error_clear(); - error = git_buf_sets(&key, "remote.origin.url"); - } else { - error = git_buf_join3( - &key, '.', "remote", remote_name.ptr, "url"); - git_buf_dispose(&remote_name); - } - - if (!error) - error = git_config__update_entry(cfg, key.ptr, sm->url, true, false); - - git_repository_free(smrepo); + if (lookup_head_remote_key(&remote_name, smrepo) == 0) { + if ((error = git_buf_join3(&key, '.', "remote", remote_name.ptr, "url")) < 0) + goto out; + } else if ((error = git_buf_sets(&key, "remote.origin.url")) < 0) { + goto out; } - git_buf_dispose(&key); + if ((error = git_config__update_entry(cfg, key.ptr, url.ptr, true, false)) < 0) + goto out; +out: + git_repository_free(smrepo); + git_buf_dispose(&remote_name); + git_buf_dispose(&key); + git_buf_dispose(&url); return error; } @@ -1570,43 +1602,40 @@ static int submodule_update_head(git_submodule *submodule) int git_submodule_reload(git_submodule *sm, int force) { - int error = 0, isvalid; - git_config *mods; + git_config *mods = NULL; + int error; GIT_UNUSED(force); assert(sm); - isvalid = git_submodule_name_is_valid(sm->repo, sm->name, 0); - if (isvalid <= 0) { + if ((error = git_submodule_name_is_valid(sm->repo, sm->name, 0)) <= 0) /* This should come with a warning, but we've no API for that */ - return isvalid; - } + goto out; - if (!git_repository_is_bare(sm->repo)) { - /* refresh config data */ - if ((error = gitmodules_snapshot(&mods, sm->repo)) < 0 && error != GIT_ENOTFOUND) - return error; - if (mods != NULL) { - error = submodule_read_config(sm, mods); - git_config_free(mods); + if (git_repository_is_bare(sm->repo)) + goto out; - if (error < 0) - return error; - } + /* refresh config data */ + if ((error = gitmodules_snapshot(&mods, sm->repo)) < 0 && error != GIT_ENOTFOUND) + goto out; - /* refresh wd data */ - sm->flags &= - ~(GIT_SUBMODULE_STATUS_IN_WD | - GIT_SUBMODULE_STATUS__WD_OID_VALID | - GIT_SUBMODULE_STATUS__WD_FLAGS); + if (mods != NULL && (error = submodule_read_config(sm, mods)) < 0) + goto out; - error = submodule_load_from_wd_lite(sm); - } + /* refresh wd data */ + sm->flags &= + ~(GIT_SUBMODULE_STATUS_IN_WD | + GIT_SUBMODULE_STATUS__WD_OID_VALID | + GIT_SUBMODULE_STATUS__WD_FLAGS); - if (error == 0 && (error = submodule_update_index(sm)) == 0) - error = submodule_update_head(sm); + if ((error = submodule_load_from_wd_lite(sm)) < 0 || + (error = submodule_update_index(sm)) < 0 || + (error = submodule_update_head(sm)) < 0) + goto out; +out: + git_config_free(mods); return error; } @@ -1935,7 +1964,6 @@ static int submodule_load_each(const git_config_entry *entry, void *payload) { lfc_data *data = payload; const char *namestart, *property; - size_t pos; git_strmap *map = data->map; git_buf name = GIT_BUF_INIT; git_submodule *sm; @@ -1967,8 +1995,7 @@ static int submodule_load_each(const git_config_entry *entry, void *payload) * a new submodule, load the config and insert it. If it's * already inserted, we've already loaded it, so we skip. */ - pos = git_strmap_lookup_index(map, name.ptr); - if (git_strmap_valid_index(map, pos)) { + if (git_strmap_exists(map, name.ptr)) { error = 0; goto done; } @@ -1981,9 +2008,7 @@ static int submodule_load_each(const git_config_entry *entry, void *payload) goto done; } - git_strmap_insert(map, sm->name, sm, &error); - assert(error != 0); - if (error < 0) + if ((error = git_strmap_set(map, sm->name, sm)) < 0) goto done; error = 0; @@ -2136,7 +2161,7 @@ static int lookup_default_remote(git_remote **remote, git_repository *repo) int error = lookup_head_remote(remote, repo); /* if that failed, use 'origin' instead */ - if (error == GIT_ENOTFOUND) + if (error == GIT_ENOTFOUND || error == GIT_EUNBORNBRANCH) error = git_remote_lookup(remote, repo, "origin"); if (error == GIT_ENOTFOUND) diff --git a/src/submodule.h b/src/submodule.h index 91a4e1223..57d95c3fc 100644 --- a/src/submodule.h +++ b/src/submodule.h @@ -11,7 +11,7 @@ #include "git2/submodule.h" #include "git2/repository.h" -#include "fileops.h" +#include "futils.h" /* Notes: * diff --git a/src/sysdir.c b/src/sysdir.c index e07ba7199..9e86dc88c 100644 --- a/src/sysdir.c +++ b/src/sysdir.c @@ -82,15 +82,25 @@ static int git_sysdir_guess_global_dirs(git_buf *out) #else int error; uid_t uid, euid; + const char *sandbox_id; uid = getuid(); euid = geteuid(); + /** + * If APP_SANDBOX_CONTAINER_ID is set, we are running in a + * sandboxed environment on macOS. + */ + sandbox_id = getenv("APP_SANDBOX_CONTAINER_ID"); + /* * In case we are running setuid, use the configuration * of the effective user. + * + * If we are running in a sandboxed environment on macOS, + * we have to get the HOME dir from the password entry file. */ - if (uid == euid) + if (!sandbox_id && uid == euid) error = git__getenv(out, "HOME"); else error = get_passwd_home(out, euid); diff --git a/src/tag.c b/src/tag.c index b4a5015df..a7a005ca1 100644 --- a/src/tag.c +++ b/src/tag.c @@ -10,6 +10,7 @@ #include "commit.h" #include "signature.h" #include "message.h" +#include "wildmatch.h" #include "git2/object.h" #include "git2/repository.h" #include "git2/signature.h" @@ -327,7 +328,7 @@ int git_tag_create_lightweight( return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0); } -int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) +int git_tag_create_from_buffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) { git_tag tag; int error; @@ -475,7 +476,7 @@ static int tag_list_cb(const char *tag_name, git_oid *oid, void *data) GIT_UNUSED(oid); if (!*filter->pattern || - p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0) + wildmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0) { char *matched = git__strdup(tag_name + GIT_REFS_TAGS_DIR_LEN); GIT_ERROR_CHECK_ALLOC(matched); @@ -520,3 +521,10 @@ int git_tag_peel(git_object **tag_target, const git_tag *tag) { return git_object_peel(tag_target, (const git_object *)tag, GIT_OBJECT_ANY); } + +/* Deprecated Functions */ + +int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) +{ + return git_tag_create_from_buffer(oid, repo, buffer, allow_ref_overwrite); +} diff --git a/src/trace.c b/src/trace.c index f2f353891..ec6a90aad 100644 --- a/src/trace.c +++ b/src/trace.c @@ -17,7 +17,7 @@ struct git_trace_data git_trace__data = {0}; #endif -int git_trace_set(git_trace_level_t level, git_trace_callback callback) +int git_trace_set(git_trace_level_t level, git_trace_cb callback) { #ifdef GIT_TRACE assert(level == 0 || callback != NULL); diff --git a/src/trace.h b/src/trace.h index 1eaf6c92a..e15118ef5 100644 --- a/src/trace.h +++ b/src/trace.h @@ -16,7 +16,7 @@ struct git_trace_data { git_trace_level_t level; - git_trace_callback callback; + git_trace_cb callback; }; extern struct git_trace_data git_trace__data; @@ -25,7 +25,7 @@ GIT_INLINE(void) git_trace__write_fmt( git_trace_level_t level, const char *fmt, ...) { - git_trace_callback callback = git_trace__data.callback; + git_trace_cb callback = git_trace__data.callback; git_buf message = GIT_BUF_INIT; va_list ap; @@ -56,7 +56,7 @@ GIT_INLINE(void) git_trace__null( GIT_UNUSED(fmt); } -#define git_trace_level() ((void)0) +#define git_trace_level() ((git_trace_level_t)0) #define git_trace git_trace__null #endif diff --git a/src/trailer.c b/src/trailer.c index dc8d1abcb..ca81fd020 100644 --- a/src/trailer.c +++ b/src/trailer.c @@ -42,15 +42,19 @@ static const char *next_line(const char *str) } /* - * Return the position of the start of the last line. If len is 0, return -1. + * Return the position of the start of the last line. If len is 0, return 0. */ -static int last_line(const char *buf, size_t len) +static bool last_line(size_t *out, const char *buf, size_t len) { - int i; + size_t i; + + *out = 0; + if (len == 0) - return -1; + return false; if (len == 1) - return 0; + return true; + /* * Skip the last character (in addition to the null terminator), * because if the last character is a newline, it is considered as part @@ -58,31 +62,37 @@ static int last_line(const char *buf, size_t len) */ i = len - 2; - for (; i >= 0; i--) { - if (buf[i] == '\n') - return i + 1; + for (; i > 0; i--) { + if (buf[i] == '\n') { + *out = i + 1; + return true; + } } - return 0; + return true; } /* * If the given line is of the form - * "..." or "...", return the - * location of the separator. Otherwise, return -1. The optional whitespace - * is allowed there primarily to allow things like "Bug #43" where is - * "Bug" and is "#". + * "..." or "...", sets out + * to the location of the separator and returns true. Otherwise, returns + * false. The optional whitespace is allowed there primarily to allow things + * like "Bug #43" where is "Bug" and is "#". * - * The separator-starts-line case (in which this function returns 0) is - * distinguished from the non-well-formed-line case (in which this function - * returns -1) because some callers of this function need such a distinction. + * The separator-starts-line case (in which this function returns true and + * sets out to 0) is distinguished from the non-well-formed-line case (in + * which this function returns false) because some callers of this function + * need such a distinction. */ -static int find_separator(const char *line, const char *separators) +static bool find_separator(size_t *out, const char *line, const char *separators) { int whitespace_found = 0; const char *c; for (c = line; *c; c++) { - if (strchr(separators, *c)) - return c - line; + if (strchr(separators, *c)) { + *out = c - line; + return true; + } + if (!whitespace_found && (isalnum(*c) || *c == '-')) continue; if (c != line && (*c == ' ' || *c == '\t')) { @@ -91,7 +101,7 @@ static int find_separator(const char *line, const char *separators) } break; } - return -1; + return false; } /* @@ -104,10 +114,9 @@ static int find_separator(const char *line, const char *separators) * Returns the number of bytes from the tail to ignore, to be fed as * the second parameter to append_signoff(). */ -static int ignore_non_trailer(const char *buf, size_t len) +static size_t ignore_non_trailer(const char *buf, size_t len) { - int boc = 0; - size_t bol = 0; + size_t boc = 0, bol = 0; int in_old_conflicts_block = 0; size_t cutoff = len; @@ -144,7 +153,7 @@ static int ignore_non_trailer(const char *buf, size_t len) * Return the position of the start of the patch or the length of str if there * is no patch in the message. */ -static int find_patch_start(const char *str) +static size_t find_patch_start(const char *str) { const char *s; @@ -160,10 +169,11 @@ static int find_patch_start(const char *str) * Return the position of the first trailer line or len if there are no * trailers. */ -static int find_trailer_start(const char *buf, size_t len) +static size_t find_trailer_start(const char *buf, size_t len) { const char *s; - int end_of_title, l, only_spaces = 1; + size_t end_of_title, l; + int only_spaces = 1; int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0; /* * Number of possible continuation lines encountered. This will be @@ -189,12 +199,11 @@ static int find_trailer_start(const char *buf, size_t len) * trailers, or (ii) contains at least one Git-generated trailer and * consists of at least 25% trailers. */ - for (l = last_line(buf, len); - l >= end_of_title; - l = last_line(buf, l)) { + l = len; + while (last_line(&l, buf, l) && l >= end_of_title) { const char *bol = buf + l; const char *const *p; - int separator_pos; + size_t separator_pos = 0; if (bol[0] == COMMENT_LINE_CHAR) { non_trailer_lines += possible_continuation_lines; @@ -223,7 +232,7 @@ static int find_trailer_start(const char *buf, size_t len) } } - separator_pos = find_separator(bol, TRAILER_SEPARATORS); + find_separator(&separator_pos, bol, TRAILER_SEPARATORS); if (separator_pos >= 1 && !isspace(bol[0])) { trailer_lines++; possible_continuation_lines = 0; @@ -244,7 +253,7 @@ continue_outer_loop: } /* Return the position of the end of the trailers. */ -static int find_trailer_end(const char *buf, size_t len) +static size_t find_trailer_end(const char *buf, size_t len) { return len - ignore_non_trailer(buf, len); } @@ -258,6 +267,9 @@ static char *extract_trailer_block(const char *message, size_t* len) size_t trailer_len = trailer_end - trailer_start; char *buffer = git__malloc(trailer_len + 1); + if (buffer == NULL) + return NULL; + memcpy(buffer, message + trailer_start, trailer_len); buffer[trailer_len] = 0; @@ -293,6 +305,8 @@ int git_message_trailers(git_message_trailer_array *trailer_arr, const char *mes size_t trailer_len; char *trailer = extract_trailer_block(message, &trailer_len); + if (trailer == NULL) + return -1; for (ptr = trailer;;) { switch (state) { diff --git a/src/transaction.c b/src/transaction.c index d8e38d803..7367d1240 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -84,7 +84,7 @@ int git_transaction_new(git_transaction **out, git_repository *repo) goto on_error; } - if ((error = git_strmap_alloc(&tx->locks)) < 0) { + if ((error = git_strmap_new(&tx->locks)) < 0) { error = -1; goto on_error; } @@ -119,8 +119,7 @@ int git_transaction_lock_ref(git_transaction *tx, const char *refname) if ((error = git_refdb_lock(&node->payload, tx->db, refname)) < 0) return error; - git_strmap_insert(tx->locks, node->name, node, &error); - if (error < 0) + if ((error = git_strmap_set(tx->locks, node->name, node)) < 0) goto cleanup; return 0; @@ -134,16 +133,12 @@ cleanup: static int find_locked(transaction_node **out, git_transaction *tx, const char *refname) { transaction_node *node; - size_t pos; - pos = git_strmap_lookup_index(tx->locks, refname); - if (!git_strmap_valid_index(tx->locks, pos)) { + if ((node = git_strmap_get(tx->locks, refname)) == NULL) { git_error_set(GIT_ERROR_REFERENCE, "the specified reference is not locked"); return GIT_ENOTFOUND; } - node = git_strmap_value_at(tx->locks, pos); - *out = node; return 0; } @@ -339,7 +334,13 @@ int git_transaction_commit(git_transaction *tx) return error; } - if (node->ref_type != GIT_REFERENCE_INVALID) { + if (node->ref_type == GIT_REFERENCE_INVALID) { + /* ref was locked but not modified */ + if ((error = git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL)) < 0) { + return error; + } + node->committed = true; + } else { if ((error = update_target(tx->db, node)) < 0) return error; } diff --git a/src/transports/auth.c b/src/transports/auth.c index 6c69282b4..4aa3df021 100644 --- a/src/transports/auth.c +++ b/src/transports/auth.c @@ -9,32 +9,31 @@ #include "git2.h" #include "buffer.h" +#include "git2/sys/credential.h" static int basic_next_token( git_buf *out, git_http_auth_context *ctx, - const char *header_name, - git_cred *c) + git_credential *c) { - git_cred_userpass_plaintext *cred; + git_credential_userpass_plaintext *cred; git_buf raw = GIT_BUF_INIT; int error = -1; GIT_UNUSED(ctx); - if (c->credtype != GIT_CREDTYPE_USERPASS_PLAINTEXT) { + if (c->credtype != GIT_CREDENTIAL_USERPASS_PLAINTEXT) { git_error_set(GIT_ERROR_INVALID, "invalid credential type for basic auth"); goto on_error; } - cred = (git_cred_userpass_plaintext *)c; + cred = (git_credential_userpass_plaintext *)c; git_buf_printf(&raw, "%s:%s", cred->username, cred->password); if (git_buf_oom(&raw) || - git_buf_printf(out, "%s: Basic ", header_name) < 0 || - git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0 || - git_buf_puts(out, "\r\n") < 0) + git_buf_puts(out, "Basic ") < 0 || + git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0) goto on_error; error = 0; @@ -48,28 +47,30 @@ on_error: } static git_http_auth_context basic_context = { - GIT_AUTHTYPE_BASIC, - GIT_CREDTYPE_USERPASS_PLAINTEXT, + GIT_HTTP_AUTH_BASIC, + GIT_CREDENTIAL_USERPASS_PLAINTEXT, + 0, NULL, basic_next_token, + NULL, NULL }; int git_http_auth_basic( - git_http_auth_context **out, const gitno_connection_data *connection_data) + git_http_auth_context **out, const git_net_url *url) { - GIT_UNUSED(connection_data); + GIT_UNUSED(url); *out = &basic_context; return 0; } int git_http_auth_dummy( - git_http_auth_context **out, const gitno_connection_data *connection_data) + git_http_auth_context **out, const git_net_url *url) { - GIT_UNUSED(connection_data); + GIT_UNUSED(url); *out = NULL; - return 0; + return GIT_PASSTHROUGH; } diff --git a/src/transports/auth.h b/src/transports/auth.h index e5cf7eff0..9caac4676 100644 --- a/src/transports/auth.h +++ b/src/transports/auth.h @@ -14,24 +14,31 @@ #include "netops.h" typedef enum { - GIT_AUTHTYPE_BASIC = 1, - GIT_AUTHTYPE_NEGOTIATE = 2, -} git_http_authtype_t; + GIT_HTTP_AUTH_BASIC = 1, + GIT_HTTP_AUTH_NEGOTIATE = 2, + GIT_HTTP_AUTH_NTLM = 4, +} git_http_auth_t; typedef struct git_http_auth_context git_http_auth_context; struct git_http_auth_context { /** Type of scheme */ - git_http_authtype_t type; + git_http_auth_t type; /** Supported credentials */ - git_credtype_t credtypes; + git_credential_t credtypes; + + /** Connection affinity or request affinity */ + unsigned connection_affinity : 1; /** Sets the challenge on the authentication context */ int (*set_challenge)(git_http_auth_context *ctx, const char *challenge); /** Gets the next authentication token from the context */ - int (*next_token)(git_buf *out, git_http_auth_context *ctx, const char *header_name, git_cred *cred); + int (*next_token)(git_buf *out, git_http_auth_context *ctx, git_credential *cred); + + /** Examines if all tokens have been presented. */ + int (*is_complete)(git_http_auth_context *ctx); /** Frees the authentication context */ void (*free)(git_http_auth_context *ctx); @@ -39,26 +46,26 @@ struct git_http_auth_context { typedef struct { /** Type of scheme */ - git_http_authtype_t type; + git_http_auth_t type; /** Name of the scheme (as used in the Authorization header) */ const char *name; /** Credential types this scheme supports */ - git_credtype_t credtypes; + git_credential_t credtypes; /** Function to initialize an authentication context */ int (*init_context)( git_http_auth_context **out, - const gitno_connection_data *connection_data); + const git_net_url *url); } git_http_auth_scheme; int git_http_auth_dummy( git_http_auth_context **out, - const gitno_connection_data *connection_data); + const git_net_url *url); int git_http_auth_basic( git_http_auth_context **out, - const gitno_connection_data *connection_data); + const git_net_url *url); #endif diff --git a/src/transports/auth_negotiate.c b/src/transports/auth_negotiate.c index 25c865c15..8a614b81a 100644 --- a/src/transports/auth_negotiate.c +++ b/src/transports/auth_negotiate.c @@ -7,14 +7,19 @@ #include "auth_negotiate.h" -#ifdef GIT_GSSAPI +#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) #include "git2.h" #include "buffer.h" #include "auth.h" +#include "git2/sys/credential.h" +#ifdef GIT_GSSFRAMEWORK +#import +#elif defined(GIT_GSSAPI) #include #include +#endif static gss_OID_desc negotiate_oid_spnego = { 6, (void *) "\x2b\x06\x01\x05\x05\x02" }; @@ -70,11 +75,26 @@ static int negotiate_set_challenge( return 0; } +static void negotiate_context_dispose(http_auth_negotiate_context *ctx) +{ + OM_uint32 status_minor; + + if (ctx->gss_context != GSS_C_NO_CONTEXT) { + gss_delete_sec_context( + &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER); + ctx->gss_context = GSS_C_NO_CONTEXT; + } + + git_buf_dispose(&ctx->target); + + git__free(ctx->challenge); + ctx->challenge = NULL; +} + static int negotiate_next_token( git_buf *buf, git_http_auth_context *c, - const char *header_name, - git_cred *cred) + git_credential *cred) { http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; OM_uint32 status_major, status_minor; @@ -88,7 +108,7 @@ static int negotiate_next_token( size_t challenge_len; int error = 0; - assert(buf && ctx && ctx->configured && cred && cred->credtype == GIT_CREDTYPE_DEFAULT); + assert(buf && ctx && ctx->configured && cred && cred->credtype == GIT_CREDENTIAL_DEFAULT); if (ctx->complete) return 0; @@ -101,18 +121,20 @@ static int negotiate_next_token( if (GSS_ERROR(status_major)) { negotiate_err_set(status_major, status_minor, - "Could not parse principal"); + "could not parse principal"); error = -1; goto done; } challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0; - if (challenge_len < 9) { - git_error_set(GIT_ERROR_NET, "no negotiate challenge sent from server"); + if (challenge_len < 9 || memcmp(ctx->challenge, "Negotiate", 9) != 0) { + git_error_set(GIT_ERROR_NET, "server did not request negotiate"); error = -1; goto done; - } else if (challenge_len > 9) { + } + + if (challenge_len > 9) { if (git_buf_decode_base64(&input_buf, ctx->challenge + 10, challenge_len - 10) < 0) { git_error_set(GIT_ERROR_NET, "invalid negotiate challenge from server"); @@ -124,14 +146,12 @@ static int negotiate_next_token( input_token.length = input_buf.size; input_token_ptr = &input_token; } else if (ctx->gss_context != GSS_C_NO_CONTEXT) { - git_error_set(GIT_ERROR_NET, "could not restart authentication"); - error = -1; - goto done; + negotiate_context_dispose(ctx); } mech = &negotiate_oid_spnego; - if (GSS_ERROR(status_major = gss_init_sec_context( + status_major = gss_init_sec_context( &status_minor, GSS_C_NO_CREDENTIAL, &ctx->gss_context, @@ -144,21 +164,29 @@ static int negotiate_next_token( NULL, &output_token, NULL, - NULL))) { - negotiate_err_set(status_major, status_minor, "Negotiate failure"); + NULL); + + if (GSS_ERROR(status_major)) { + negotiate_err_set(status_major, status_minor, "negotiate failure"); error = -1; goto done; } /* This message merely told us auth was complete; we do not respond. */ if (status_major == GSS_S_COMPLETE) { + negotiate_context_dispose(ctx); ctx->complete = 1; goto done; } - git_buf_printf(buf, "%s: Negotiate ", header_name); + if (output_token.length == 0) { + git_error_set(GIT_ERROR_NET, "GSSAPI did not return token"); + error = -1; + goto done; + } + + git_buf_puts(buf, "Negotiate "); git_buf_encode_base64(buf, output_token.value, output_token.length); - git_buf_puts(buf, "\r\n"); if (git_buf_oom(buf)) error = -1; @@ -170,20 +198,20 @@ done: return error; } +static int negotiate_is_complete(git_http_auth_context *c) +{ + http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + + assert(ctx); + + return (ctx->complete == 1); +} + static void negotiate_context_free(git_http_auth_context *c) { http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; - OM_uint32 status_minor; - if (ctx->gss_context != GSS_C_NO_CONTEXT) { - gss_delete_sec_context( - &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER); - ctx->gss_context = GSS_C_NO_CONTEXT; - } - - git_buf_dispose(&ctx->target); - - git__free(ctx->challenge); + negotiate_context_dispose(ctx); ctx->configured = 0; ctx->complete = 0; @@ -194,7 +222,7 @@ static void negotiate_context_free(git_http_auth_context *c) static int negotiate_init_context( http_auth_negotiate_context *ctx, - const gitno_connection_data *connection_data) + const git_net_url *url) { OM_uint32 status_major, status_minor; gss_OID item, *oid; @@ -202,8 +230,9 @@ static int negotiate_init_context( size_t i; /* Query supported mechanisms looking for SPNEGO) */ - if (GSS_ERROR(status_major = - gss_indicate_mechs(&status_minor, &mechanism_list))) { + status_major = gss_indicate_mechs(&status_minor, &mechanism_list); + + if (GSS_ERROR(status_major)) { negotiate_err_set(status_major, status_minor, "could not query mechanisms"); return -1; @@ -235,7 +264,7 @@ static int negotiate_init_context( } git_buf_puts(&ctx->target, "HTTP@"); - git_buf_puts(&ctx->target, connection_data->host); + git_buf_puts(&ctx->target, url->host); if (git_buf_oom(&ctx->target)) return -1; @@ -248,7 +277,7 @@ static int negotiate_init_context( int git_http_auth_negotiate( git_http_auth_context **out, - const gitno_connection_data *connection_data) + const git_net_url *url) { http_auth_negotiate_context *ctx; @@ -257,15 +286,17 @@ int git_http_auth_negotiate( ctx = git__calloc(1, sizeof(http_auth_negotiate_context)); GIT_ERROR_CHECK_ALLOC(ctx); - if (negotiate_init_context(ctx, connection_data) < 0) { + if (negotiate_init_context(ctx, url) < 0) { git__free(ctx); return -1; } - ctx->parent.type = GIT_AUTHTYPE_NEGOTIATE; - ctx->parent.credtypes = GIT_CREDTYPE_DEFAULT; + ctx->parent.type = GIT_HTTP_AUTH_NEGOTIATE; + ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT; + ctx->parent.connection_affinity = 1; ctx->parent.set_challenge = negotiate_set_challenge; ctx->parent.next_token = negotiate_next_token; + ctx->parent.is_complete = negotiate_is_complete; ctx->parent.free = negotiate_context_free; *out = (git_http_auth_context *)ctx; diff --git a/src/transports/auth_negotiate.h b/src/transports/auth_negotiate.h index 15a528aaf..34aff295b 100644 --- a/src/transports/auth_negotiate.h +++ b/src/transports/auth_negotiate.h @@ -12,11 +12,11 @@ #include "git2.h" #include "auth.h" -#ifdef GIT_GSSAPI +#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) extern int git_http_auth_negotiate( git_http_auth_context **out, - const gitno_connection_data *connection_data); + const git_net_url *url); #else diff --git a/src/transports/auth_ntlm.c b/src/transports/auth_ntlm.c new file mode 100644 index 000000000..d134a3db6 --- /dev/null +++ b/src/transports/auth_ntlm.c @@ -0,0 +1,223 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2.h" +#include "common.h" +#include "buffer.h" +#include "auth.h" +#include "auth_ntlm.h" +#include "git2/sys/credential.h" + +#ifdef GIT_NTLM + +#include "ntlm.h" + +typedef struct { + git_http_auth_context parent; + ntlm_client *ntlm; + char *challenge; + bool complete; +} http_auth_ntlm_context; + +static int ntlm_set_challenge( + git_http_auth_context *c, + const char *challenge) +{ + http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; + + assert(ctx && challenge); + + git__free(ctx->challenge); + + ctx->challenge = git__strdup(challenge); + GIT_ERROR_CHECK_ALLOC(ctx->challenge); + + return 0; +} + +static int ntlm_set_credentials(http_auth_ntlm_context *ctx, git_credential *_cred) +{ + git_credential_userpass_plaintext *cred; + const char *sep, *username; + char *domain = NULL, *domainuser = NULL; + int error = 0; + + assert(_cred->credtype == GIT_CREDENTIAL_USERPASS_PLAINTEXT); + cred = (git_credential_userpass_plaintext *)_cred; + + if ((sep = strchr(cred->username, '\\')) != NULL) { + domain = git__strndup(cred->username, (sep - cred->username)); + GIT_ERROR_CHECK_ALLOC(domain); + + domainuser = git__strdup(sep + 1); + GIT_ERROR_CHECK_ALLOC(domainuser); + + username = domainuser; + } else { + username = cred->username; + } + + if (ntlm_client_set_credentials(ctx->ntlm, + username, domain, cred->password) < 0) { + git_error_set(GIT_ERROR_NET, "could not set credentials: %s", + ntlm_client_errmsg(ctx->ntlm)); + error = -1; + goto done; + } + +done: + git__free(domain); + git__free(domainuser); + return error; +} + +static int ntlm_next_token( + git_buf *buf, + git_http_auth_context *c, + git_credential *cred) +{ + http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; + git_buf input_buf = GIT_BUF_INIT; + const unsigned char *msg; + size_t challenge_len, msg_len; + int error = -1; + + assert(buf && ctx && ctx->ntlm); + + challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0; + + if (ctx->complete) + ntlm_client_reset(ctx->ntlm); + + /* + * Set us complete now since it's the default case; the one + * incomplete case (successfully created a client request) + * will explicitly set that it requires a second step. + */ + ctx->complete = true; + + if (cred && ntlm_set_credentials(ctx, cred) != 0) + goto done; + + if (challenge_len < 4) { + git_error_set(GIT_ERROR_NET, "no ntlm challenge sent from server"); + goto done; + } else if (challenge_len == 4) { + if (memcmp(ctx->challenge, "NTLM", 4) != 0) { + git_error_set(GIT_ERROR_NET, "server did not request NTLM"); + goto done; + } + + if (ntlm_client_negotiate(&msg, &msg_len, ctx->ntlm) != 0) { + git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s", + ntlm_client_errmsg(ctx->ntlm)); + goto done; + } + + ctx->complete = false; + } else { + if (memcmp(ctx->challenge, "NTLM ", 5) != 0) { + git_error_set(GIT_ERROR_NET, "challenge from server was not NTLM"); + goto done; + } + + if (git_buf_decode_base64(&input_buf, + ctx->challenge + 5, challenge_len - 5) < 0) { + git_error_set(GIT_ERROR_NET, "invalid NTLM challenge from server"); + goto done; + } + + if (ntlm_client_set_challenge(ctx->ntlm, + (const unsigned char *)input_buf.ptr, input_buf.size) != 0) { + git_error_set(GIT_ERROR_NET, "ntlm challenge failed: %s", + ntlm_client_errmsg(ctx->ntlm)); + goto done; + } + + if (ntlm_client_response(&msg, &msg_len, ctx->ntlm) != 0) { + git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s", + ntlm_client_errmsg(ctx->ntlm)); + goto done; + } + } + + git_buf_puts(buf, "NTLM "); + git_buf_encode_base64(buf, (const char *)msg, msg_len); + + if (git_buf_oom(buf)) + goto done; + + error = 0; + +done: + git_buf_dispose(&input_buf); + return error; +} + +static int ntlm_is_complete(git_http_auth_context *c) +{ + http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; + + assert(ctx); + return (ctx->complete == true); +} + +static void ntlm_context_free(git_http_auth_context *c) +{ + http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; + + ntlm_client_free(ctx->ntlm); + git__free(ctx->challenge); + git__free(ctx); +} + +static int ntlm_init_context( + http_auth_ntlm_context *ctx, + const git_net_url *url) +{ + GIT_UNUSED(url); + + if ((ctx->ntlm = ntlm_client_init(NTLM_CLIENT_DEFAULTS)) == NULL) { + git_error_set_oom(); + return -1; + } + + return 0; +} + +int git_http_auth_ntlm( + git_http_auth_context **out, + const git_net_url *url) +{ + http_auth_ntlm_context *ctx; + + GIT_UNUSED(url); + + *out = NULL; + + ctx = git__calloc(1, sizeof(http_auth_ntlm_context)); + GIT_ERROR_CHECK_ALLOC(ctx); + + if (ntlm_init_context(ctx, url) < 0) { + git__free(ctx); + return -1; + } + + ctx->parent.type = GIT_HTTP_AUTH_NTLM; + ctx->parent.credtypes = GIT_CREDENTIAL_USERPASS_PLAINTEXT; + ctx->parent.connection_affinity = 1; + ctx->parent.set_challenge = ntlm_set_challenge; + ctx->parent.next_token = ntlm_next_token; + ctx->parent.is_complete = ntlm_is_complete; + ctx->parent.free = ntlm_context_free; + + *out = (git_http_auth_context *)ctx; + + return 0; +} + +#endif /* GIT_NTLM */ diff --git a/src/transports/auth_ntlm.h b/src/transports/auth_ntlm.h new file mode 100644 index 000000000..a7cd6d795 --- /dev/null +++ b/src/transports/auth_ntlm.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_transports_auth_ntlm_h__ +#define INCLUDE_transports_auth_ntlm_h__ + +#include "git2.h" +#include "auth.h" + +/* NTLM requires a full request/challenge/response */ +#define GIT_AUTH_STEPS_NTLM 2 + +#ifdef GIT_NTLM + +#if defined(GIT_OPENSSL) +# define CRYPT_OPENSSL +#elif defined(GIT_MBEDTLS) +# define CRYPT_MBEDTLS +#elif defined(GIT_SECURE_TRANSPORT) +# define CRYPT_COMMONCRYPTO +#endif + +extern int git_http_auth_ntlm( + git_http_auth_context **out, + const git_net_url *url); + +#else + +#define git_http_auth_ntlm git_http_auth_dummy + +#endif /* GIT_NTLM */ + +#endif + diff --git a/src/transports/cred.c b/src/transports/cred.c deleted file mode 100644 index dfacc96d3..000000000 --- a/src/transports/cred.c +++ /dev/null @@ -1,390 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "cred.h" - -#include "git2.h" -#include "smart.h" -#include "git2/cred_helpers.h" - -static int git_cred_ssh_key_type_new( - git_cred **cred, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase, - git_credtype_t credtype); - -int git_cred_has_username(git_cred *cred) -{ - if (cred->credtype == GIT_CREDTYPE_DEFAULT) - return 0; - - return 1; -} - -const char *git_cred__username(git_cred *cred) -{ - switch (cred->credtype) { - case GIT_CREDTYPE_USERNAME: - { - git_cred_username *c = (git_cred_username *) cred; - return c->username; - } - case GIT_CREDTYPE_USERPASS_PLAINTEXT: - { - git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *) cred; - return c->username; - } - case GIT_CREDTYPE_SSH_KEY: - case GIT_CREDTYPE_SSH_MEMORY: - { - git_cred_ssh_key *c = (git_cred_ssh_key *) cred; - return c->username; - } - case GIT_CREDTYPE_SSH_CUSTOM: - { - git_cred_ssh_custom *c = (git_cred_ssh_custom *) cred; - return c->username; - } - case GIT_CREDTYPE_SSH_INTERACTIVE: - { - git_cred_ssh_interactive *c = (git_cred_ssh_interactive *) cred; - return c->username; - } - - default: - return NULL; - } -} - -static void plaintext_free(struct git_cred *cred) -{ - git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; - - git__free(c->username); - - /* Zero the memory which previously held the password */ - if (c->password) { - size_t pass_len = strlen(c->password); - git__memzero(c->password, pass_len); - git__free(c->password); - } - - git__free(c); -} - -int git_cred_userpass_plaintext_new( - git_cred **cred, - const char *username, - const char *password) -{ - git_cred_userpass_plaintext *c; - - assert(cred && username && password); - - c = git__malloc(sizeof(git_cred_userpass_plaintext)); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT; - c->parent.free = plaintext_free; - c->username = git__strdup(username); - - if (!c->username) { - git__free(c); - return -1; - } - - c->password = git__strdup(password); - - if (!c->password) { - git__free(c->username); - git__free(c); - return -1; - } - - *cred = &c->parent; - return 0; -} - -static void ssh_key_free(struct git_cred *cred) -{ - git_cred_ssh_key *c = - (git_cred_ssh_key *)cred; - - git__free(c->username); - - if (c->privatekey) { - /* Zero the memory which previously held the private key */ - size_t key_len = strlen(c->privatekey); - git__memzero(c->privatekey, key_len); - git__free(c->privatekey); - } - - if (c->passphrase) { - /* Zero the memory which previously held the passphrase */ - size_t pass_len = strlen(c->passphrase); - git__memzero(c->passphrase, pass_len); - git__free(c->passphrase); - } - - if (c->publickey) { - /* Zero the memory which previously held the public key */ - size_t key_len = strlen(c->publickey); - git__memzero(c->publickey, key_len); - git__free(c->publickey); - } - - git__free(c); -} - -static void ssh_interactive_free(struct git_cred *cred) -{ - git_cred_ssh_interactive *c = (git_cred_ssh_interactive *)cred; - - git__free(c->username); - - git__free(c); -} - -static void ssh_custom_free(struct git_cred *cred) -{ - git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred; - - git__free(c->username); - - if (c->publickey) { - /* Zero the memory which previously held the publickey */ - size_t key_len = strlen(c->publickey); - git__memzero(c->publickey, key_len); - git__free(c->publickey); - } - - git__free(c); -} - -static void default_free(struct git_cred *cred) -{ - git_cred_default *c = (git_cred_default *)cred; - - git__free(c); -} - -static void username_free(struct git_cred *cred) -{ - git__free(cred); -} - -int git_cred_ssh_key_new( - git_cred **cred, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase) -{ - return git_cred_ssh_key_type_new( - cred, - username, - publickey, - privatekey, - passphrase, - GIT_CREDTYPE_SSH_KEY); -} - -int git_cred_ssh_key_memory_new( - git_cred **cred, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase) -{ -#ifdef GIT_SSH_MEMORY_CREDENTIALS - return git_cred_ssh_key_type_new( - cred, - username, - publickey, - privatekey, - passphrase, - GIT_CREDTYPE_SSH_MEMORY); -#else - GIT_UNUSED(cred); - GIT_UNUSED(username); - GIT_UNUSED(publickey); - GIT_UNUSED(privatekey); - GIT_UNUSED(passphrase); - - git_error_set(GIT_ERROR_INVALID, - "this version of libgit2 was not built with ssh memory credentials."); - return -1; -#endif -} - -static int git_cred_ssh_key_type_new( - git_cred **cred, - const char *username, - const char *publickey, - const char *privatekey, - const char *passphrase, - git_credtype_t credtype) -{ - git_cred_ssh_key *c; - - assert(username && cred && privatekey); - - c = git__calloc(1, sizeof(git_cred_ssh_key)); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = credtype; - c->parent.free = ssh_key_free; - - c->username = git__strdup(username); - GIT_ERROR_CHECK_ALLOC(c->username); - - c->privatekey = git__strdup(privatekey); - GIT_ERROR_CHECK_ALLOC(c->privatekey); - - if (publickey) { - c->publickey = git__strdup(publickey); - GIT_ERROR_CHECK_ALLOC(c->publickey); - } - - if (passphrase) { - c->passphrase = git__strdup(passphrase); - GIT_ERROR_CHECK_ALLOC(c->passphrase); - } - - *cred = &c->parent; - return 0; -} - -int git_cred_ssh_interactive_new( - git_cred **out, - const char *username, - git_cred_ssh_interactive_callback prompt_callback, - void *payload) -{ - git_cred_ssh_interactive *c; - - assert(out && username && prompt_callback); - - c = git__calloc(1, sizeof(git_cred_ssh_interactive)); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = GIT_CREDTYPE_SSH_INTERACTIVE; - c->parent.free = ssh_interactive_free; - - c->username = git__strdup(username); - GIT_ERROR_CHECK_ALLOC(c->username); - - c->prompt_callback = prompt_callback; - c->payload = payload; - - *out = &c->parent; - return 0; -} - -int git_cred_ssh_key_from_agent(git_cred **cred, const char *username) { - git_cred_ssh_key *c; - - assert(username && cred); - - c = git__calloc(1, sizeof(git_cred_ssh_key)); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = GIT_CREDTYPE_SSH_KEY; - c->parent.free = ssh_key_free; - - c->username = git__strdup(username); - GIT_ERROR_CHECK_ALLOC(c->username); - - c->privatekey = NULL; - - *cred = &c->parent; - return 0; -} - -int git_cred_ssh_custom_new( - git_cred **cred, - const char *username, - const char *publickey, - size_t publickey_len, - git_cred_sign_callback sign_callback, - void *payload) -{ - git_cred_ssh_custom *c; - - assert(username && cred); - - c = git__calloc(1, sizeof(git_cred_ssh_custom)); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = GIT_CREDTYPE_SSH_CUSTOM; - c->parent.free = ssh_custom_free; - - c->username = git__strdup(username); - GIT_ERROR_CHECK_ALLOC(c->username); - - if (publickey_len > 0) { - c->publickey = git__malloc(publickey_len); - GIT_ERROR_CHECK_ALLOC(c->publickey); - - memcpy(c->publickey, publickey, publickey_len); - } - - c->publickey_len = publickey_len; - c->sign_callback = sign_callback; - c->payload = payload; - - *cred = &c->parent; - return 0; -} - -int git_cred_default_new(git_cred **cred) -{ - git_cred_default *c; - - assert(cred); - - c = git__calloc(1, sizeof(git_cred_default)); - GIT_ERROR_CHECK_ALLOC(c); - - c->credtype = GIT_CREDTYPE_DEFAULT; - c->free = default_free; - - *cred = c; - return 0; -} - -int git_cred_username_new(git_cred **cred, const char *username) -{ - git_cred_username *c; - size_t len, allocsize; - - assert(cred); - - len = strlen(username); - - GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, sizeof(git_cred_username), len); - GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, allocsize, 1); - c = git__malloc(allocsize); - GIT_ERROR_CHECK_ALLOC(c); - - c->parent.credtype = GIT_CREDTYPE_USERNAME; - c->parent.free = username_free; - memcpy(c->username, username, len + 1); - - *cred = (git_cred *) c; - return 0; -} - -void git_cred_free(git_cred *cred) -{ - if (!cred) - return; - - cred->free(cred); -} diff --git a/src/transports/credential.c b/src/transports/credential.c new file mode 100644 index 000000000..0cf50a029 --- /dev/null +++ b/src/transports/credential.c @@ -0,0 +1,476 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" + +#include "git2/credential.h" +#include "git2/sys/credential.h" +#include "git2/credential_helpers.h" + +static int git_credential_ssh_key_type_new( + git_credential **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase, + git_credential_t credtype); + +int git_credential_has_username(git_credential *cred) +{ + if (cred->credtype == GIT_CREDENTIAL_DEFAULT) + return 0; + + return 1; +} + +const char *git_credential_get_username(git_credential *cred) +{ + switch (cred->credtype) { + case GIT_CREDENTIAL_USERNAME: + { + git_credential_username *c = (git_credential_username *) cred; + return c->username; + } + case GIT_CREDENTIAL_USERPASS_PLAINTEXT: + { + git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *) cred; + return c->username; + } + case GIT_CREDENTIAL_SSH_KEY: + case GIT_CREDENTIAL_SSH_MEMORY: + { + git_credential_ssh_key *c = (git_credential_ssh_key *) cred; + return c->username; + } + case GIT_CREDENTIAL_SSH_CUSTOM: + { + git_credential_ssh_custom *c = (git_credential_ssh_custom *) cred; + return c->username; + } + case GIT_CREDENTIAL_SSH_INTERACTIVE: + { + git_credential_ssh_interactive *c = (git_credential_ssh_interactive *) cred; + return c->username; + } + + default: + return NULL; + } +} + +static void plaintext_free(struct git_credential *cred) +{ + git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; + + git__free(c->username); + + /* Zero the memory which previously held the password */ + if (c->password) { + size_t pass_len = strlen(c->password); + git__memzero(c->password, pass_len); + git__free(c->password); + } + + git__free(c); +} + +int git_credential_userpass_plaintext_new( + git_credential **cred, + const char *username, + const char *password) +{ + git_credential_userpass_plaintext *c; + + assert(cred && username && password); + + c = git__malloc(sizeof(git_credential_userpass_plaintext)); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDENTIAL_USERPASS_PLAINTEXT; + c->parent.free = plaintext_free; + c->username = git__strdup(username); + + if (!c->username) { + git__free(c); + return -1; + } + + c->password = git__strdup(password); + + if (!c->password) { + git__free(c->username); + git__free(c); + return -1; + } + + *cred = &c->parent; + return 0; +} + +static void ssh_key_free(struct git_credential *cred) +{ + git_credential_ssh_key *c = + (git_credential_ssh_key *)cred; + + git__free(c->username); + + if (c->privatekey) { + /* Zero the memory which previously held the private key */ + size_t key_len = strlen(c->privatekey); + git__memzero(c->privatekey, key_len); + git__free(c->privatekey); + } + + if (c->passphrase) { + /* Zero the memory which previously held the passphrase */ + size_t pass_len = strlen(c->passphrase); + git__memzero(c->passphrase, pass_len); + git__free(c->passphrase); + } + + if (c->publickey) { + /* Zero the memory which previously held the public key */ + size_t key_len = strlen(c->publickey); + git__memzero(c->publickey, key_len); + git__free(c->publickey); + } + + git__free(c); +} + +static void ssh_interactive_free(struct git_credential *cred) +{ + git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred; + + git__free(c->username); + + git__free(c); +} + +static void ssh_custom_free(struct git_credential *cred) +{ + git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred; + + git__free(c->username); + + if (c->publickey) { + /* Zero the memory which previously held the publickey */ + size_t key_len = strlen(c->publickey); + git__memzero(c->publickey, key_len); + git__free(c->publickey); + } + + git__free(c); +} + +static void default_free(struct git_credential *cred) +{ + git_credential_default *c = (git_credential_default *)cred; + + git__free(c); +} + +static void username_free(struct git_credential *cred) +{ + git__free(cred); +} + +int git_credential_ssh_key_new( + git_credential **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ + return git_credential_ssh_key_type_new( + cred, + username, + publickey, + privatekey, + passphrase, + GIT_CREDENTIAL_SSH_KEY); +} + +int git_credential_ssh_key_memory_new( + git_credential **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ +#ifdef GIT_SSH_MEMORY_CREDENTIALS + return git_credential_ssh_key_type_new( + cred, + username, + publickey, + privatekey, + passphrase, + GIT_CREDENTIAL_SSH_MEMORY); +#else + GIT_UNUSED(cred); + GIT_UNUSED(username); + GIT_UNUSED(publickey); + GIT_UNUSED(privatekey); + GIT_UNUSED(passphrase); + + git_error_set(GIT_ERROR_INVALID, + "this version of libgit2 was not built with ssh memory credentials."); + return -1; +#endif +} + +static int git_credential_ssh_key_type_new( + git_credential **cred, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase, + git_credential_t credtype) +{ + git_credential_ssh_key *c; + + assert(username && cred && privatekey); + + c = git__calloc(1, sizeof(git_credential_ssh_key)); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = credtype; + c->parent.free = ssh_key_free; + + c->username = git__strdup(username); + GIT_ERROR_CHECK_ALLOC(c->username); + + c->privatekey = git__strdup(privatekey); + GIT_ERROR_CHECK_ALLOC(c->privatekey); + + if (publickey) { + c->publickey = git__strdup(publickey); + GIT_ERROR_CHECK_ALLOC(c->publickey); + } + + if (passphrase) { + c->passphrase = git__strdup(passphrase); + GIT_ERROR_CHECK_ALLOC(c->passphrase); + } + + *cred = &c->parent; + return 0; +} + +int git_credential_ssh_interactive_new( + git_credential **out, + const char *username, + git_credential_ssh_interactive_cb prompt_callback, + void *payload) +{ + git_credential_ssh_interactive *c; + + assert(out && username && prompt_callback); + + c = git__calloc(1, sizeof(git_credential_ssh_interactive)); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDENTIAL_SSH_INTERACTIVE; + c->parent.free = ssh_interactive_free; + + c->username = git__strdup(username); + GIT_ERROR_CHECK_ALLOC(c->username); + + c->prompt_callback = prompt_callback; + c->payload = payload; + + *out = &c->parent; + return 0; +} + +int git_credential_ssh_key_from_agent(git_credential **cred, const char *username) { + git_credential_ssh_key *c; + + assert(username && cred); + + c = git__calloc(1, sizeof(git_credential_ssh_key)); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDENTIAL_SSH_KEY; + c->parent.free = ssh_key_free; + + c->username = git__strdup(username); + GIT_ERROR_CHECK_ALLOC(c->username); + + c->privatekey = NULL; + + *cred = &c->parent; + return 0; +} + +int git_credential_ssh_custom_new( + git_credential **cred, + const char *username, + const char *publickey, + size_t publickey_len, + git_credential_sign_cb sign_callback, + void *payload) +{ + git_credential_ssh_custom *c; + + assert(username && cred); + + c = git__calloc(1, sizeof(git_credential_ssh_custom)); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDENTIAL_SSH_CUSTOM; + c->parent.free = ssh_custom_free; + + c->username = git__strdup(username); + GIT_ERROR_CHECK_ALLOC(c->username); + + if (publickey_len > 0) { + c->publickey = git__malloc(publickey_len); + GIT_ERROR_CHECK_ALLOC(c->publickey); + + memcpy(c->publickey, publickey, publickey_len); + } + + c->publickey_len = publickey_len; + c->sign_callback = sign_callback; + c->payload = payload; + + *cred = &c->parent; + return 0; +} + +int git_credential_default_new(git_credential **cred) +{ + git_credential_default *c; + + assert(cred); + + c = git__calloc(1, sizeof(git_credential_default)); + GIT_ERROR_CHECK_ALLOC(c); + + c->credtype = GIT_CREDENTIAL_DEFAULT; + c->free = default_free; + + *cred = c; + return 0; +} + +int git_credential_username_new(git_credential **cred, const char *username) +{ + git_credential_username *c; + size_t len, allocsize; + + assert(cred); + + len = strlen(username); + + GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, sizeof(git_credential_username), len); + GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, allocsize, 1); + c = git__malloc(allocsize); + GIT_ERROR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDENTIAL_USERNAME; + c->parent.free = username_free; + memcpy(c->username, username, len + 1); + + *cred = (git_credential *) c; + return 0; +} + +void git_credential_free(git_credential *cred) +{ + if (!cred) + return; + + cred->free(cred); +} + +/* Deprecated credential functions */ + +int git_cred_has_username(git_credential *cred) +{ + return git_credential_has_username(cred); +} + +const char *git_cred_get_username(git_credential *cred) +{ + return git_credential_get_username(cred); +} + +int git_cred_userpass_plaintext_new( + git_credential **out, + const char *username, + const char *password) +{ + return git_credential_userpass_plaintext_new(out,username, password); +} + +int git_cred_default_new(git_credential **out) +{ + return git_credential_default_new(out); +} + +int git_cred_username_new(git_credential **out, const char *username) +{ + return git_credential_username_new(out, username); +} + +int git_cred_ssh_key_new( + git_credential **out, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ + return git_credential_ssh_key_new(out, username, + publickey, privatekey, passphrase); +} + +int git_cred_ssh_key_memory_new( + git_credential **out, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase) +{ + return git_credential_ssh_key_memory_new(out, username, + publickey, privatekey, passphrase); +} + +int git_cred_ssh_interactive_new( + git_credential **out, + const char *username, + git_credential_ssh_interactive_cb prompt_callback, + void *payload) +{ + return git_credential_ssh_interactive_new(out, username, + prompt_callback, payload); +} + +int git_cred_ssh_key_from_agent( + git_credential **out, + const char *username) +{ + return git_credential_ssh_key_from_agent(out, username); +} + +int git_cred_ssh_custom_new( + git_credential **out, + const char *username, + const char *publickey, + size_t publickey_len, + git_credential_sign_cb sign_callback, + void *payload) +{ + return git_credential_ssh_custom_new(out, username, + publickey, publickey_len, sign_callback, payload); +} + +void git_cred_free(git_credential *cred) +{ + git_credential_free(cred); +} diff --git a/src/transports/cred_helpers.c b/src/transports/credential_helpers.c similarity index 63% rename from src/transports/cred_helpers.c rename to src/transports/credential_helpers.c index fdc56b17e..6b975e126 100644 --- a/src/transports/cred_helpers.c +++ b/src/transports/credential_helpers.c @@ -7,16 +7,16 @@ #include "common.h" -#include "git2/cred_helpers.h" +#include "git2/credential_helpers.h" -int git_cred_userpass( - git_cred **cred, +int git_credential_userpass( + git_credential **cred, const char *url, const char *user_from_url, unsigned int allowed_types, void *payload) { - git_cred_userpass_payload *userpass = (git_cred_userpass_payload*)payload; + git_credential_userpass_payload *userpass = (git_credential_userpass_payload*)payload; const char *effective_username = NULL; GIT_UNUSED(url); @@ -42,12 +42,25 @@ int git_cred_userpass( else return -1; - if (GIT_CREDTYPE_USERNAME & allowed_types) - return git_cred_username_new(cred, effective_username); + if (GIT_CREDENTIAL_USERNAME & allowed_types) + return git_credential_username_new(cred, effective_username); - if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 || - git_cred_userpass_plaintext_new(cred, effective_username, userpass->password) < 0) + if ((GIT_CREDENTIAL_USERPASS_PLAINTEXT & allowed_types) == 0 || + git_credential_userpass_plaintext_new(cred, effective_username, userpass->password) < 0) return -1; return 0; } + +/* Deprecated credential functions */ + +int git_cred_userpass( + git_credential **out, + const char *url, + const char *user_from_url, + unsigned int allowed_types, + void *payload) +{ + return git_credential_userpass(out, url, user_from_url, + allowed_types, payload); +} diff --git a/src/transports/git.c b/src/transports/git.c index 9fd3b47fc..e48b7f961 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -192,8 +192,9 @@ static int _git_uploadpack_ls( const char *url, git_smart_subtransport_stream **stream) { - char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; + git_net_url urldata = GIT_NET_URL_INIT; const char *stream_url = url; + const char *host, *port; git_proto_stream *s; int error; @@ -202,17 +203,15 @@ static int _git_uploadpack_ls( if (!git__prefixcmp(url, prefix_git)) stream_url += strlen(prefix_git); - if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0) + if ((error = git_net_url_parse(&urldata, url)) < 0) return error; + host = urldata.host; + port = urldata.port ? urldata.port : GIT_DEFAULT_PORT; + error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream); - git__free(host); - git__free(port); - git__free(path); - git__free(user); - git__free(pass); - + git_net_url_dispose(&urldata); if (error < 0) { git_proto_stream_free(*stream); @@ -251,7 +250,7 @@ static int _git_receivepack_ls( const char *url, git_smart_subtransport_stream **stream) { - char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; + git_net_url urldata = GIT_NET_URL_INIT; const char *stream_url = url; git_proto_stream *s; int error; @@ -260,16 +259,12 @@ static int _git_receivepack_ls( if (!git__prefixcmp(url, prefix_git)) stream_url += strlen(prefix_git); - if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0) + if ((error = git_net_url_parse(&urldata, url)) < 0) return error; - error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, host, port, stream); + error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, urldata.host, urldata.port, stream); - git__free(host); - git__free(port); - git__free(path); - git__free(user); - git__free(pass); + git_net_url_dispose(&urldata); if (error < 0) { git_proto_stream_free(*stream); diff --git a/src/transports/http.c b/src/transports/http.c index 80ba5ba73..66731b0ce 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -12,1363 +12,635 @@ #include "git2.h" #include "http_parser.h" #include "buffer.h" +#include "net.h" #include "netops.h" #include "global.h" #include "remote.h" +#include "git2/sys/credential.h" #include "smart.h" #include "auth.h" #include "http.h" #include "auth_negotiate.h" +#include "auth_ntlm.h" +#include "trace.h" #include "streams/tls.h" #include "streams/socket.h" +#include "httpclient.h" -git_http_auth_scheme auth_schemes[] = { - { GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate }, - { GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic }, +bool git_http__expect_continue = false; + +typedef enum { + HTTP_STATE_NONE = 0, + HTTP_STATE_SENDING_REQUEST, + HTTP_STATE_RECEIVING_RESPONSE, + HTTP_STATE_DONE +} http_state; + +typedef struct { + git_http_method method; + const char *url; + const char *request_type; + const char *response_type; + unsigned chunked : 1; +} http_service; + +typedef struct { + git_smart_subtransport_stream parent; + const http_service *service; + http_state state; + unsigned replay_count; +} http_stream; + +typedef struct { + git_net_url url; + + git_credential *cred; + unsigned auth_schemetypes; + unsigned url_cred_presented : 1; +} http_server; + +typedef struct { + git_smart_subtransport parent; + transport_smart *owner; + + http_server server; + http_server proxy; + + git_http_client *http_client; +} http_subtransport; + +static const http_service upload_pack_ls_service = { + GIT_HTTP_METHOD_GET, "/info/refs?service=git-upload-pack", + NULL, + "application/x-git-upload-pack-advertisement", + 0 +}; +static const http_service upload_pack_service = { + GIT_HTTP_METHOD_POST, "/git-upload-pack", + "application/x-git-upload-pack-request", + "application/x-git-upload-pack-result", + 0 +}; +static const http_service receive_pack_ls_service = { + GIT_HTTP_METHOD_GET, "/info/refs?service=git-receive-pack", + NULL, + "application/x-git-receive-pack-advertisement", + 0 +}; +static const http_service receive_pack_service = { + GIT_HTTP_METHOD_POST, "/git-receive-pack", + "application/x-git-receive-pack-request", + "application/x-git-receive-pack-result", + 1 }; - -static const char *upload_pack_service = "upload-pack"; -static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; -static const char *upload_pack_service_url = "/git-upload-pack"; -static const char *receive_pack_service = "receive-pack"; -static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack"; -static const char *receive_pack_service_url = "/git-receive-pack"; -static const char *get_verb = "GET"; -static const char *post_verb = "POST"; - -#define AUTH_HEADER_SERVER "Authorization" -#define AUTH_HEADER_PROXY "Proxy-Authorization" #define SERVER_TYPE_REMOTE "remote" #define SERVER_TYPE_PROXY "proxy" #define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport) -#define PARSE_ERROR_GENERIC -1 -#define PARSE_ERROR_REPLAY -2 -/** Look at the user field */ -#define PARSE_ERROR_EXT -3 - -#define CHUNK_SIZE 4096 - -enum last_cb { - NONE, - FIELD, - VALUE -}; - -typedef struct { - git_smart_subtransport_stream parent; - const char *service; - const char *service_url; - char *redirect_url; - const char *verb; - char *chunk_buffer; - unsigned chunk_buffer_len; - unsigned sent_request : 1, - received_response : 1, - chunked : 1; -} http_stream; - -typedef struct { - gitno_connection_data url; - git_stream *stream; - - git_cred *cred; - git_cred *url_cred; - - git_vector auth_challenges; - git_vector auth_contexts; -} http_server; - -typedef struct { - git_smart_subtransport parent; - transport_smart *owner; - git_stream *gitserver_stream; - bool connected; - - http_server server; - - http_server proxy; - char *proxy_url; - git_proxy_options proxy_opts; - - /* Parser structures */ - http_parser parser; - http_parser_settings settings; - gitno_buffer parse_buffer; - git_buf parse_header_name; - git_buf parse_header_value; - char parse_buffer_data[NETIO_BUFSIZE]; - char *content_type; - char *content_length; - char *location; - enum last_cb last_cb; - int parse_error; - int error; - unsigned parse_finished : 1, - replay_count : 3; -} http_subtransport; - -typedef struct { - http_stream *s; - http_subtransport *t; - - /* Target buffer details from read() */ - char *buffer; - size_t buf_size; - size_t *bytes_read; -} parser_context; - -static bool credtype_match(git_http_auth_scheme *scheme, void *data) +static int apply_url_credentials( + git_credential **cred, + unsigned int allowed_types, + const char *username, + const char *password) { - unsigned int credtype = *(unsigned int *)data; + if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) + return git_credential_userpass_plaintext_new(cred, username, password); - return !!(scheme->credtypes & credtype); + if ((allowed_types & GIT_CREDENTIAL_DEFAULT) && *username == '\0' && *password == '\0') + return git_credential_default_new(cred); + + return GIT_PASSTHROUGH; } -static bool challenge_match(git_http_auth_scheme *scheme, void *data) -{ - const char *scheme_name = scheme->name; - const char *challenge = (const char *)data; - size_t scheme_len; - - scheme_len = strlen(scheme_name); - return (strncasecmp(challenge, scheme_name, scheme_len) == 0 && - (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' ')); -} - -static int auth_context_match( - git_http_auth_context **out, - http_server *server, - bool (*scheme_match)(git_http_auth_scheme *scheme, void *data), - void *data) -{ - git_http_auth_scheme *scheme = NULL; - git_http_auth_context *context = NULL, *c; - size_t i; - - *out = NULL; - - for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) { - if (scheme_match(&auth_schemes[i], data)) { - scheme = &auth_schemes[i]; - break; - } - } - - if (!scheme) - return 0; - - /* See if authentication has already started for this scheme */ - git_vector_foreach(&server->auth_contexts, i, c) { - if (c->type == scheme->type) { - context = c; - break; - } - } - - if (!context) { - if (scheme->init_context(&context, &server->url) < 0) - return -1; - else if (!context) - return 0; - else if (git_vector_insert(&server->auth_contexts, context) < 0) - return -1; - } - - *out = context; - - return 0; -} - -static int apply_credentials( - git_buf *buf, - http_server *server, - const char *header_name) -{ - git_cred *cred = server->cred; - git_http_auth_context *context; - - /* Apply the credentials given to us in the URL */ - if (!cred && server->url.user && server->url.pass) { - if (!server->url_cred && - git_cred_userpass_plaintext_new(&server->url_cred, - server->url.user, server->url.pass) < 0) - return -1; - - cred = server->url_cred; - } - - if (!cred) - return 0; - - /* Get or create a context for the best scheme for this cred type */ - if (auth_context_match(&context, server, - credtype_match, &cred->credtype) < 0) - return -1; - - if (!context) - return 0; - - return context->next_token(buf, context, header_name, cred); -} - -static int gen_request( - git_buf *buf, - http_stream *s, - size_t content_length) -{ - http_subtransport *t = OWNING_SUBTRANSPORT(s); - const char *path = t->server.url.path ? t->server.url.path : "/"; - size_t i; - - if (t->proxy_opts.type == GIT_PROXY_SPECIFIED) - git_buf_printf(buf, "%s %s://%s:%s%s%s HTTP/1.1\r\n", - s->verb, - t->server.url.use_ssl ? "https" : "http", - t->server.url.host, - t->server.url.port, - path, s->service_url); - else - git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", - s->verb, path, s->service_url); - - git_buf_puts(buf, "User-Agent: "); - git_http__user_agent(buf); - git_buf_puts(buf, "\r\n"); - git_buf_printf(buf, "Host: %s", t->server.url.host); - if (strcmp(t->server.url.port, gitno__default_port(&t->server.url)) != 0) { - git_buf_printf(buf, ":%s", t->server.url.port); - } - git_buf_puts(buf, "\r\n"); - - if (s->chunked || content_length > 0) { - git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", s->service); - git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", s->service); - - if (s->chunked) - git_buf_puts(buf, "Transfer-Encoding: chunked\r\n"); - else - git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length); - } else - git_buf_puts(buf, "Accept: */*\r\n"); - - for (i = 0; i < t->owner->custom_headers.count; i++) { - if (t->owner->custom_headers.strings[i]) - git_buf_printf(buf, "%s\r\n", t->owner->custom_headers.strings[i]); - } - - /* Apply proxy and server credentials to the request */ - if (t->proxy_opts.type != GIT_PROXY_NONE && - apply_credentials(buf, &t->proxy, AUTH_HEADER_PROXY) < 0) - return -1; - - if (apply_credentials(buf, &t->server, AUTH_HEADER_SERVER) < 0) - return -1; - - git_buf_puts(buf, "\r\n"); - - if (git_buf_oom(buf)) - return -1; - - return 0; -} - -static int parse_authenticate_response( - http_server *server, - int *allowed_types) -{ - git_http_auth_context *context; - char *challenge; - size_t i; - - git_vector_foreach(&server->auth_challenges, i, challenge) { - if (auth_context_match(&context, server, - challenge_match, challenge) < 0) - return -1; - else if (!context) - continue; - - if (context->set_challenge && - context->set_challenge(context, challenge) < 0) - return -1; - - *allowed_types |= context->credtypes; - } - - return 0; -} - -static int on_header_ready(http_subtransport *t) -{ - git_buf *name = &t->parse_header_name; - git_buf *value = &t->parse_header_value; - - if (!strcasecmp("Content-Type", git_buf_cstr(name))) { - if (t->content_type) { - git_error_set(GIT_ERROR_NET, "multiple Content-Type headers"); - return -1; - } - - t->content_type = git__strdup(git_buf_cstr(value)); - GIT_ERROR_CHECK_ALLOC(t->content_type); - } - else if (!strcasecmp("Content-Length", git_buf_cstr(name))) { - if (t->content_length) { - git_error_set(GIT_ERROR_NET, "multiple Content-Length headers"); - return -1; - } - - t->content_length = git__strdup(git_buf_cstr(value)); - GIT_ERROR_CHECK_ALLOC(t->content_length); - } - else if (!strcasecmp("Proxy-Authenticate", git_buf_cstr(name))) { - char *dup = git__strdup(git_buf_cstr(value)); - GIT_ERROR_CHECK_ALLOC(dup); - - if (git_vector_insert(&t->proxy.auth_challenges, dup) < 0) - return -1; - } - else if (!strcasecmp("WWW-Authenticate", git_buf_cstr(name))) { - char *dup = git__strdup(git_buf_cstr(value)); - GIT_ERROR_CHECK_ALLOC(dup); - - if (git_vector_insert(&t->server.auth_challenges, dup) < 0) - return -1; - } - else if (!strcasecmp("Location", git_buf_cstr(name))) { - if (t->location) { - git_error_set(GIT_ERROR_NET, "multiple Location headers"); - return -1; - } - - t->location = git__strdup(git_buf_cstr(value)); - GIT_ERROR_CHECK_ALLOC(t->location); - } - - return 0; -} - -static int on_header_field(http_parser *parser, const char *str, size_t len) -{ - parser_context *ctx = (parser_context *) parser->data; - http_subtransport *t = ctx->t; - - /* Both parse_header_name and parse_header_value are populated - * and ready for consumption */ - if (VALUE == t->last_cb) - if (on_header_ready(t) < 0) - return t->parse_error = PARSE_ERROR_GENERIC; - - if (NONE == t->last_cb || VALUE == t->last_cb) - git_buf_clear(&t->parse_header_name); - - if (git_buf_put(&t->parse_header_name, str, len) < 0) - return t->parse_error = PARSE_ERROR_GENERIC; - - t->last_cb = FIELD; - return 0; -} - -static int on_header_value(http_parser *parser, const char *str, size_t len) -{ - parser_context *ctx = (parser_context *) parser->data; - http_subtransport *t = ctx->t; - - assert(NONE != t->last_cb); - - if (FIELD == t->last_cb) - git_buf_clear(&t->parse_header_value); - - if (git_buf_put(&t->parse_header_value, str, len) < 0) - return t->parse_error = PARSE_ERROR_GENERIC; - - t->last_cb = VALUE; - return 0; -} - -GIT_INLINE(void) free_cred(git_cred **cred) +GIT_INLINE(void) free_cred(git_credential **cred) { if (*cred) { - git_cred_free(*cred); + git_credential_free(*cred); (*cred) = NULL; } } -static int on_auth_required( - git_cred **creds, - http_parser *parser, +static int handle_auth( + http_server *server, + const char *server_type, const char *url, - const char *type, - git_cred_acquire_cb callback, - void *callback_payload, - const char *username, - int allowed_types) + unsigned int allowed_schemetypes, + unsigned int allowed_credtypes, + git_credential_acquire_cb callback, + void *callback_payload) { - parser_context *ctx = (parser_context *) parser->data; - http_subtransport *t = ctx->t; - int ret; + int error = 1; - if (!allowed_types) { - git_error_set(GIT_ERROR_NET, "%s requested authentication but did not negotiate mechanisms", type); - t->parse_error = PARSE_ERROR_GENERIC; - return t->parse_error; + if (server->cred) + free_cred(&server->cred); + + /* Start with URL-specified credentials, if there were any. */ + if ((allowed_credtypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT) && + !server->url_cred_presented && + server->url.username && + server->url.password) { + error = apply_url_credentials(&server->cred, allowed_credtypes, server->url.username, server->url.password); + server->url_cred_presented = 1; + + /* treat GIT_PASSTHROUGH as if callback isn't set */ + if (error == GIT_PASSTHROUGH) + error = 1; } - if (callback) { - free_cred(creds); - ret = callback(creds, url, username, allowed_types, callback_payload); + if (error > 0 && callback) { + error = callback(&server->cred, url, server->url.username, allowed_credtypes, callback_payload); - if (ret == GIT_PASSTHROUGH) { - /* treat GIT_PASSTHROUGH as if callback isn't set */ - } else if (ret < 0) { - t->error = ret; - t->parse_error = PARSE_ERROR_EXT; - return t->parse_error; - } else { - assert(*creds); - - if (!((*creds)->credtype & allowed_types)) { - git_error_set(GIT_ERROR_NET, "%s credential provider returned an invalid cred type", type); - t->parse_error = PARSE_ERROR_GENERIC; - return t->parse_error; - } - - /* Successfully acquired a credential. */ - t->parse_error = PARSE_ERROR_REPLAY; - return 0; - } + /* treat GIT_PASSTHROUGH as if callback isn't set */ + if (error == GIT_PASSTHROUGH) + error = 1; } - git_error_set(GIT_ERROR_NET, "%s authentication required but no callback set", - type); - t->parse_error = PARSE_ERROR_GENERIC; - return t->parse_error; + if (error > 0) { + git_error_set(GIT_ERROR_HTTP, "%s authentication required but no callback set", server_type); + error = -1; + } + + if (!error) + server->auth_schemetypes = allowed_schemetypes; + + return error; } -static int on_headers_complete(http_parser *parser) +GIT_INLINE(int) handle_remote_auth( + http_stream *stream, + git_http_response *response) { - parser_context *ctx = (parser_context *) parser->data; - http_subtransport *t = ctx->t; - http_stream *s = ctx->s; - git_buf buf = GIT_BUF_INIT; - int proxy_auth_types = 0, server_auth_types = 0; + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - /* Enforce a reasonable cap on the number of replays */ - if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) { - git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays"); - return t->parse_error = PARSE_ERROR_GENERIC; + if (response->server_auth_credtypes == 0) { + git_error_set(GIT_ERROR_HTTP, "server requires authentication that we do not support"); + return -1; } - /* Both parse_header_name and parse_header_value are populated - * and ready for consumption. */ - if (VALUE == t->last_cb) - if (on_header_ready(t) < 0) - return t->parse_error = PARSE_ERROR_GENERIC; + /* Otherwise, prompt for credentials. */ + return handle_auth( + &transport->server, + SERVER_TYPE_REMOTE, + transport->owner->url, + response->server_auth_schemetypes, + response->server_auth_credtypes, + transport->owner->cred_acquire_cb, + transport->owner->cred_acquire_payload); +} - /* - * Capture authentication headers for the proxy or final endpoint, - * these may be 407/401 (authentication is not complete) or a 200 - * (informing us that auth has completed). - */ - if (parse_authenticate_response(&t->proxy, &proxy_auth_types) < 0 || - parse_authenticate_response(&t->server, &server_auth_types) < 0) - return t->parse_error = PARSE_ERROR_GENERIC; +GIT_INLINE(int) handle_proxy_auth( + http_stream *stream, + git_http_response *response) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); - /* Check for a proxy authentication failure. */ - if (parser->status_code == 407 && get_verb == s->verb) - return on_auth_required(&t->proxy.cred, - parser, - t->proxy_opts.url, - SERVER_TYPE_PROXY, - t->proxy_opts.credentials, - t->proxy_opts.payload, - t->proxy.url.user, - proxy_auth_types); + if (response->proxy_auth_credtypes == 0) { + git_error_set(GIT_ERROR_HTTP, "proxy requires authentication that we do not support"); + return -1; + } - /* Check for an authentication failure. */ - if (parser->status_code == 401 && get_verb == s->verb) - return on_auth_required(&t->server.cred, - parser, - t->owner->url, - SERVER_TYPE_REMOTE, - t->owner->cred_acquire_cb, - t->owner->cred_acquire_payload, - t->server.url.user, - server_auth_types); + /* Otherwise, prompt for credentials. */ + return handle_auth( + &transport->proxy, + SERVER_TYPE_PROXY, + transport->owner->proxy.url, + response->server_auth_schemetypes, + response->proxy_auth_credtypes, + transport->owner->proxy.credentials, + transport->owner->proxy.payload); +} - /* Check for a redirect. - * Right now we only permit a redirect to the same hostname. */ - if ((parser->status_code == 301 || - parser->status_code == 302 || - (parser->status_code == 303 && get_verb == s->verb) || - parser->status_code == 307 || - parser->status_code == 308) && - t->location) { - if (gitno_connection_data_from_url(&t->server.url, t->location, s->service_url) < 0) - return t->parse_error = PARSE_ERROR_GENERIC; +static int handle_response( + bool *complete, + http_stream *stream, + git_http_response *response, + bool allow_replay) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + int error; - /* Set the redirect URL on the stream. This is a transfer of - * ownership of the memory. */ - if (s->redirect_url) - git__free(s->redirect_url); + *complete = false; - s->redirect_url = t->location; - t->location = NULL; + if (allow_replay && git_http_response_is_redirect(response)) { + if (!response->location) { + git_error_set(GIT_ERROR_HTTP, "redirect without location"); + return -1; + } + + if (git_net_url_apply_redirect(&transport->server.url, response->location, stream->service->url) < 0) { + return -1; + } - t->connected = 0; - t->parse_error = PARSE_ERROR_REPLAY; return 0; + } else if (git_http_response_is_redirect(response)) { + git_error_set(GIT_ERROR_HTTP, "unexpected redirect"); + return -1; } - /* Check for a 200 HTTP status code. */ - if (parser->status_code != 200) { - git_error_set(GIT_ERROR_NET, - "unexpected HTTP status code: %d", - parser->status_code); - return t->parse_error = PARSE_ERROR_GENERIC; + /* If we're in the middle of challenge/response auth, continue. */ + if (allow_replay && response->resend_credentials) { + return 0; + } else if (allow_replay && response->status == GIT_HTTP_STATUS_UNAUTHORIZED) { + if ((error = handle_remote_auth(stream, response)) < 0) + return error; + + return git_http_client_skip_body(transport->http_client); + } else if (allow_replay && response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { + if ((error = handle_proxy_auth(stream, response)) < 0) + return error; + + return git_http_client_skip_body(transport->http_client); + } else if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED || + response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { + git_error_set(GIT_ERROR_HTTP, "unexpected authentication failure"); + return -1; + } + + if (response->status != GIT_HTTP_STATUS_OK) { + git_error_set(GIT_ERROR_HTTP, "unexpected http status code: %d", response->status); + return -1; } /* The response must contain a Content-Type header. */ - if (!t->content_type) { - git_error_set(GIT_ERROR_NET, "no Content-Type header in response"); - return t->parse_error = PARSE_ERROR_GENERIC; + if (!response->content_type) { + git_error_set(GIT_ERROR_HTTP, "no content-type header in response"); + return -1; } /* The Content-Type header must match our expectation. */ - if (get_verb == s->verb) - git_buf_printf(&buf, - "application/x-git-%s-advertisement", - ctx->s->service); - else - git_buf_printf(&buf, - "application/x-git-%s-result", - ctx->s->service); - - if (git_buf_oom(&buf)) - return t->parse_error = PARSE_ERROR_GENERIC; - - if (strcmp(t->content_type, git_buf_cstr(&buf))) { - git_buf_dispose(&buf); - git_error_set(GIT_ERROR_NET, - "invalid Content-Type: %s", - t->content_type); - return t->parse_error = PARSE_ERROR_GENERIC; - } - - git_buf_dispose(&buf); - - return 0; -} - -static int on_message_complete(http_parser *parser) -{ - parser_context *ctx = (parser_context *) parser->data; - http_subtransport *t = ctx->t; - - t->parse_finished = 1; - - return 0; -} - -static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len) -{ - parser_context *ctx = (parser_context *) parser->data; - http_subtransport *t = ctx->t; - - /* If our goal is to replay the request (either an auth failure or - * a redirect) then don't bother buffering since we're ignoring the - * content anyway. - */ - if (t->parse_error == PARSE_ERROR_REPLAY) - return 0; - - /* If there's no buffer set, we're explicitly ignoring the body. */ - if (ctx->buffer) { - if (ctx->buf_size < len) { - git_error_set(GIT_ERROR_NET, "can't fit data in the buffer"); - return t->parse_error = PARSE_ERROR_GENERIC; - } - - memcpy(ctx->buffer, str, len); - ctx->buffer += len; - ctx->buf_size -= len; - } - - *(ctx->bytes_read) += len; - - return 0; -} - -static void clear_parser_state(http_subtransport *t) -{ - http_parser_init(&t->parser, HTTP_RESPONSE); - gitno_buffer_setup_fromstream(t->server.stream, - &t->parse_buffer, - t->parse_buffer_data, - sizeof(t->parse_buffer_data)); - - t->last_cb = NONE; - t->parse_error = 0; - t->parse_finished = 0; - - git_buf_dispose(&t->parse_header_name); - git_buf_init(&t->parse_header_name, 0); - - git_buf_dispose(&t->parse_header_value); - git_buf_init(&t->parse_header_value, 0); - - git__free(t->content_type); - t->content_type = NULL; - - git__free(t->content_length); - t->content_length = NULL; - - git__free(t->location); - t->location = NULL; - - git_vector_free_deep(&t->proxy.auth_challenges); - git_vector_free_deep(&t->server.auth_challenges); -} - -static int write_chunk(git_stream *io, const char *buffer, size_t len) -{ - git_buf buf = GIT_BUF_INIT; - - /* Chunk header */ - git_buf_printf(&buf, "%" PRIxZ "\r\n", len); - - if (git_buf_oom(&buf)) - return -1; - - if (git_stream__write_full(io, buf.ptr, buf.size, 0) < 0) { - git_buf_dispose(&buf); + if (strcmp(response->content_type, stream->service->response_type) != 0) { + git_error_set(GIT_ERROR_HTTP, "invalid content-type: '%s'", response->content_type); return -1; } - git_buf_dispose(&buf); - - /* Chunk body */ - if (len > 0 && git_stream__write_full(io, buffer, len, 0) < 0) - return -1; - - /* Chunk footer */ - if (git_stream__write_full(io, "\r\n", 2, 0) < 0) - return -1; - + *complete = true; + stream->state = HTTP_STATE_RECEIVING_RESPONSE; return 0; } -static int load_proxy_config(http_subtransport *t) +static int lookup_proxy( + bool *out_use, + http_subtransport *transport) { - int error; + const char *proxy; + git_remote *remote; + bool use_ssl; + char *config = NULL; + int error = 0; - switch (t->owner->proxy.type) { - case GIT_PROXY_NONE: - return 0; + *out_use = false; + git_net_url_dispose(&transport->proxy.url); - case GIT_PROXY_AUTO: - git__free(t->proxy_url); - t->proxy_url = NULL; - - git_proxy_init_options(&t->proxy_opts, GIT_PROXY_OPTIONS_VERSION); - - if ((error = git_remote__get_http_proxy(t->owner->owner, - !!t->server.url.use_ssl, &t->proxy_url)) < 0) - return error; - - if (!t->proxy_url) - return 0; - - t->proxy_opts.type = GIT_PROXY_SPECIFIED; - t->proxy_opts.url = t->proxy_url; - t->proxy_opts.credentials = t->owner->proxy.credentials; - t->proxy_opts.certificate_check = t->owner->proxy.certificate_check; - t->proxy_opts.payload = t->owner->proxy.payload; + switch (transport->owner->proxy.type) { + case GIT_PROXY_SPECIFIED: + proxy = transport->owner->proxy.url; break; - case GIT_PROXY_SPECIFIED: - memcpy(&t->proxy_opts, &t->owner->proxy, sizeof(git_proxy_options)); + case GIT_PROXY_AUTO: + remote = transport->owner->owner; + use_ssl = !strcmp(transport->server.url.scheme, "https"); + + error = git_remote__get_http_proxy(remote, use_ssl, &config); + + if (error || !config) + goto done; + + proxy = config; break; default: - assert(0); - return -1; + return 0; } - if ((error = gitno_connection_data_from_url(&t->proxy.url, t->proxy_opts.url, NULL)) < 0) - return error; - - if (t->proxy.url.use_ssl) { - git_error_set(GIT_ERROR_NET, "SSL connections to proxy are not supported"); - return -1; - } - - return error; -} - -static int check_certificate( - git_stream *stream, - gitno_connection_data *url, - int is_valid, - git_transport_certificate_check_cb cert_cb, - void *cert_cb_payload) -{ - git_cert *cert; - git_error_state last_error = {0}; - int error; - - if ((error = git_stream_certificate(&cert, stream)) < 0) - return error; - - git_error_state_capture(&last_error, GIT_ECERTIFICATE); - - error = cert_cb(cert, is_valid, url->host, cert_cb_payload); - - if (error == GIT_PASSTHROUGH && !is_valid) - return git_error_state_restore(&last_error); - else if (error == GIT_PASSTHROUGH) - error = 0; - else if (error && !git_error_last()) - git_error_set(GIT_ERROR_NET, "user rejected certificate for %s", url->host); - - git_error_state_free(&last_error); - return error; -} - -static int stream_connect( - git_stream *stream, - gitno_connection_data *url, - git_transport_certificate_check_cb cert_cb, - void *cb_payload) -{ - int error; - - GIT_ERROR_CHECK_VERSION(stream, GIT_STREAM_VERSION, "git_stream"); - - error = git_stream_connect(stream); - - if (error && error != GIT_ECERTIFICATE) - return error; - - if (git_stream_is_encrypted(stream) && cert_cb != NULL) - error = check_certificate(stream, url, !error, cert_cb, cb_payload); - - return error; -} - -static int gen_connect_req(git_buf *buf, http_subtransport *t) -{ - git_buf_printf(buf, "CONNECT %s:%s HTTP/1.1\r\n", - t->server.url.host, t->server.url.port); - - git_buf_puts(buf, "User-Agent: "); - git_http__user_agent(buf); - git_buf_puts(buf, "\r\n"); - - git_buf_printf(buf, "Host: %s\r\n", t->proxy.url.host); - - if (apply_credentials(buf, &t->proxy, AUTH_HEADER_PROXY) < 0) - return -1; - - git_buf_puts(buf, "\r\n"); - - return git_buf_oom(buf) ? -1 : 0; -} - -static int proxy_headers_complete(http_parser *parser) -{ - parser_context *ctx = (parser_context *) parser->data; - http_subtransport *t = ctx->t; - int proxy_auth_types = 0; - - /* Enforce a reasonable cap on the number of replays */ - if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) { - git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays"); - return t->parse_error = PARSE_ERROR_GENERIC; - } - - /* Both parse_header_name and parse_header_value are populated - * and ready for consumption. */ - if (VALUE == t->last_cb) - if (on_header_ready(t) < 0) - return t->parse_error = PARSE_ERROR_GENERIC; - - /* - * Capture authentication headers for the proxy or final endpoint, - * these may be 407/401 (authentication is not complete) or a 200 - * (informing us that auth has completed). - */ - if (parse_authenticate_response(&t->proxy, &proxy_auth_types) < 0) - return t->parse_error = PARSE_ERROR_GENERIC; - - /* Check for a proxy authentication failure. */ - if (parser->status_code == 407) - return on_auth_required(&t->proxy.cred, - parser, - t->proxy_opts.url, - SERVER_TYPE_PROXY, - t->proxy_opts.credentials, - t->proxy_opts.payload, - t->proxy.url.user, - proxy_auth_types); - - if (parser->status_code != 200) { - git_error_set(GIT_ERROR_NET, "unexpected status code from proxy: %d", - parser->status_code); - return t->parse_error = PARSE_ERROR_GENERIC; - } - - if (!t->content_length || strcmp(t->content_length, "0") == 0) - t->parse_finished = 1; - - return 0; -} - -static int proxy_connect( - git_stream **out, git_stream *proxy_stream, http_subtransport *t) -{ - git_buf request = GIT_BUF_INIT; - static http_parser_settings proxy_parser_settings = {0}; - size_t bytes_read = 0, bytes_parsed; - parser_context ctx; - int error; - - /* Use the parser settings only to parser headers. */ - proxy_parser_settings.on_header_field = on_header_field; - proxy_parser_settings.on_header_value = on_header_value; - proxy_parser_settings.on_headers_complete = proxy_headers_complete; - proxy_parser_settings.on_message_complete = on_message_complete; - -replay: - clear_parser_state(t); - - gitno_buffer_setup_fromstream(proxy_stream, - &t->parse_buffer, - t->parse_buffer_data, - sizeof(t->parse_buffer_data)); - - if ((error = gen_connect_req(&request, t)) < 0) + if (!proxy || + (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0) goto done; - if ((error = git_stream__write_full(proxy_stream, request.ptr, - request.size, 0)) < 0) - goto done; - - git_buf_dispose(&request); - - while (!bytes_read && !t->parse_finished) { - t->parse_buffer.offset = 0; - - if ((error = gitno_recv(&t->parse_buffer)) < 0) - goto done; - - /* - * This call to http_parser_execute will invoke the on_* - * callbacks. Since we don't care about the body of the response, - * we can set our buffer to NULL. - */ - ctx.t = t; - ctx.s = NULL; - ctx.buffer = NULL; - ctx.buf_size = 0; - ctx.bytes_read = &bytes_read; - - /* Set the context, call the parser, then unset the context. */ - t->parser.data = &ctx; - - bytes_parsed = http_parser_execute(&t->parser, - &proxy_parser_settings, t->parse_buffer.data, t->parse_buffer.offset); - - t->parser.data = NULL; - - /* Ensure that we didn't get a redirect; unsupported. */ - if (t->location) { - git_error_set(GIT_ERROR_NET, "proxy server sent unsupported redirect during CONNECT"); - error = -1; - goto done; - } - - /* Replay the request with authentication headers. */ - if (PARSE_ERROR_REPLAY == t->parse_error) - goto replay; - - if (t->parse_error < 0) { - error = t->parse_error == PARSE_ERROR_EXT ? PARSE_ERROR_EXT : -1; - goto done; - } - - if (bytes_parsed != t->parse_buffer.offset) { - git_error_set(GIT_ERROR_NET, - "HTTP parser error: %s", - http_errno_description((enum http_errno)t->parser.http_errno)); - error = -1; - goto done; - } - } - - if ((error = git_tls_stream_wrap(out, proxy_stream, t->server.url.host)) == 0) - error = stream_connect(*out, &t->server.url, - t->owner->certificate_check_cb, - t->owner->message_cb_payload); - - /* - * Since we've connected via a HTTPS proxy tunnel, we don't behave - * as if we have an HTTP proxy. - */ - t->proxy_opts.type = GIT_PROXY_NONE; - t->replay_count = 0; + *out_use = true; done: + git__free(config); return error; } -static int http_connect(http_subtransport *t) +static int generate_request( + git_net_url *url, + git_http_request *request, + http_stream *stream, + size_t len) { - gitno_connection_data *url; - git_stream *proxy_stream = NULL, *stream = NULL; - git_transport_certificate_check_cb cert_cb; - void *cb_payload; + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + bool use_proxy = false; int error; - if (t->connected && - http_should_keep_alive(&t->parser) && - t->parse_finished) - return 0; - - if ((error = load_proxy_config(t)) < 0) + if ((error = git_net_url_joinpath(url, + &transport->server.url, stream->service->url)) < 0 || + (error = lookup_proxy(&use_proxy, transport)) < 0) return error; - if (t->server.stream) { - git_stream_close(t->server.stream); - git_stream_free(t->server.stream); - t->server.stream = NULL; + request->method = stream->service->method; + request->url = url; + request->credentials = transport->server.cred; + request->proxy = use_proxy ? &transport->proxy.url : NULL; + request->proxy_credentials = transport->proxy.cred; + request->custom_headers = &transport->owner->custom_headers; + + if (stream->service->method == GIT_HTTP_METHOD_POST) { + request->chunked = stream->service->chunked; + request->content_length = stream->service->chunked ? 0 : len; + request->content_type = stream->service->request_type; + request->accept = stream->service->response_type; + request->expect_continue = git_http__expect_continue; } - if (t->proxy.stream) { - git_stream_close(t->proxy.stream); - git_stream_free(t->proxy.stream); - t->proxy.stream = NULL; + return 0; +} + +/* + * Read from an HTTP transport - for the first invocation of this function + * (ie, when stream->state == HTTP_STATE_NONE), we'll send a GET request + * to the remote host. We will stream that data back on all subsequent + * calls. + */ +static int http_stream_read( + git_smart_subtransport_stream *s, + char *buffer, + size_t buffer_size, + size_t *out_len) +{ + http_stream *stream = (http_stream *)s; + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_net_url url = GIT_NET_URL_INIT; + git_net_url proxy_url = GIT_NET_URL_INIT; + git_http_request request = {0}; + git_http_response response = {0}; + bool complete; + int error; + + *out_len = 0; + + if (stream->state == HTTP_STATE_NONE) { + stream->state = HTTP_STATE_SENDING_REQUEST; + stream->replay_count = 0; } - t->connected = 0; - - if (t->proxy_opts.type == GIT_PROXY_SPECIFIED) { - url = &t->proxy.url; - cert_cb = t->proxy_opts.certificate_check; - cb_payload = t->proxy_opts.payload; - } else { - url = &t->server.url; - cert_cb = t->owner->certificate_check_cb; - cb_payload = t->owner->message_cb_payload; - } - - if (url->use_ssl) - error = git_tls_stream_new(&stream, url->host, url->port); - else - error = git_socket_stream_new(&stream, url->host, url->port); - - if (error < 0) - goto on_error; - - if ((error = stream_connect(stream, url, cert_cb, cb_payload)) < 0) - goto on_error; - /* - * At this point we have a connection to the remote server or to - * a proxy. If it's a proxy and the remote server is actually - * an HTTPS connection, then we need to build a CONNECT tunnel. + * Formulate the URL, send the request and read the response + * headers. Some of the request body may also be read. */ - if (t->proxy_opts.type == GIT_PROXY_SPECIFIED && - t->server.url.use_ssl) { - proxy_stream = stream; - stream = NULL; + while (stream->state == HTTP_STATE_SENDING_REQUEST && + stream->replay_count < GIT_HTTP_REPLAY_MAX) { + git_net_url_dispose(&url); + git_net_url_dispose(&proxy_url); + git_http_response_dispose(&response); - if ((error = proxy_connect(&stream, proxy_stream, t)) < 0) - goto on_error; + if ((error = generate_request(&url, &request, stream, 0)) < 0 || + (error = git_http_client_send_request( + transport->http_client, &request)) < 0 || + (error = git_http_client_read_response( + &response, transport->http_client)) < 0 || + (error = handle_response(&complete, stream, &response, true)) < 0) + goto done; + + if (complete) + break; + + stream->replay_count++; } - t->proxy.stream = proxy_stream; - t->server.stream = stream; - t->connected = 1; - t->replay_count = 0; - return 0; - -on_error: - if (stream) { - git_stream_close(stream); - git_stream_free(stream); + if (stream->state == HTTP_STATE_SENDING_REQUEST) { + git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); + error = -1; + goto done; } - if (proxy_stream) { - git_stream_close(proxy_stream); - git_stream_free(proxy_stream); + assert (stream->state == HTTP_STATE_RECEIVING_RESPONSE); + + error = git_http_client_read_body(transport->http_client, buffer, buffer_size); + + if (error > 0) { + *out_len = error; + error = 0; } +done: + git_net_url_dispose(&url); + git_net_url_dispose(&proxy_url); + git_http_response_dispose(&response); + return error; } -static int http_stream_read( - git_smart_subtransport_stream *stream, - char *buffer, - size_t buf_size, - size_t *bytes_read) +static bool needs_probe(http_stream *stream) { - http_stream *s = (http_stream *)stream; - http_subtransport *t = OWNING_SUBTRANSPORT(s); - parser_context ctx; - size_t bytes_parsed; + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); -replay: - *bytes_read = 0; + return (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM || + transport->server.auth_schemetypes == GIT_HTTP_AUTH_NEGOTIATE); +} - assert(t->connected); +static int send_probe(http_stream *stream) +{ + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_http_client *client = transport->http_client; + const char *probe = "0000"; + size_t len = 4; + git_net_url url = GIT_NET_URL_INIT; + git_http_request request = {0}; + git_http_response response = {0}; + bool complete = false; + size_t step, steps = 1; + int error; - if (!s->sent_request) { - git_buf request = GIT_BUF_INIT; + /* NTLM requires a full challenge/response */ + if (transport->server.auth_schemetypes == GIT_HTTP_AUTH_NTLM) + steps = GIT_AUTH_STEPS_NTLM; - clear_parser_state(t); + /* + * Send at most two requests: one without any authentication to see + * if we get prompted to authenticate. If we do, send a second one + * with the first authentication message. The final authentication + * message with the response will occur with the *actual* POST data. + */ + for (step = 0; step < steps && !complete; step++) { + git_net_url_dispose(&url); + git_http_response_dispose(&response); - if (gen_request(&request, s, 0) < 0) - return -1; - - if (git_stream__write_full(t->server.stream, request.ptr, - request.size, 0) < 0) { - git_buf_dispose(&request); - return -1; - } - - git_buf_dispose(&request); - - s->sent_request = 1; + if ((error = generate_request(&url, &request, stream, len)) < 0 || + (error = git_http_client_send_request(client, &request)) < 0 || + (error = git_http_client_send_body(client, probe, len)) < 0 || + (error = git_http_client_read_response(&response, client)) < 0 || + (error = git_http_client_skip_body(client)) < 0 || + (error = handle_response(&complete, stream, &response, true)) < 0) + goto done; } - if (!s->received_response) { - if (s->chunked) { - assert(s->verb == post_verb); +done: + git_http_response_dispose(&response); + git_net_url_dispose(&url); + return error; +} - /* Flush, if necessary */ - if (s->chunk_buffer_len > 0 && - write_chunk(t->server.stream, - s->chunk_buffer, s->chunk_buffer_len) < 0) - return -1; +/* +* Write to an HTTP transport - for the first invocation of this function +* (ie, when stream->state == HTTP_STATE_NONE), we'll send a POST request +* to the remote host. If we're sending chunked data, then subsequent calls +* will write the additional data given in the buffer. If we're not chunking, +* then the caller should have given us all the data in the original call. +* The caller should call http_stream_read_response to get the result. +*/ +static int http_stream_write( + git_smart_subtransport_stream *s, + const char *buffer, + size_t len) +{ + http_stream *stream = GIT_CONTAINER_OF(s, http_stream, parent); + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_net_url url = GIT_NET_URL_INIT; + git_http_request request = {0}; + git_http_response response = {0}; + int error; - s->chunk_buffer_len = 0; + while (stream->state == HTTP_STATE_NONE && + stream->replay_count < GIT_HTTP_REPLAY_MAX) { - /* Write the final chunk. */ - if (git_stream__write_full(t->server.stream, - "0\r\n\r\n", 5, 0) < 0) - return -1; - } - - s->received_response = 1; - } - - while (!*bytes_read && !t->parse_finished) { - size_t data_offset; - int error; + git_net_url_dispose(&url); + git_http_response_dispose(&response); /* - * Make the parse_buffer think it's as full of data as - * the buffer, so it won't try to recv more data than - * we can put into it. - * - * data_offset is the actual data offset from which we - * should tell the parser to start reading. + * If we're authenticating with a connection-based mechanism + * (NTLM, Kerberos), send a "probe" packet. Servers SHOULD + * authenticate an entire keep-alive connection, so ideally + * we should not need to authenticate but some servers do + * not support this. By sending a probe packet, we'll be + * able to follow up with a second POST using the actual + * data (and, in the degenerate case, the authentication + * header as well). */ - if (buf_size >= t->parse_buffer.len) { - t->parse_buffer.offset = 0; + if (needs_probe(stream) && (error = send_probe(stream)) < 0) + goto done; + + /* Send the regular POST request. */ + if ((error = generate_request(&url, &request, stream, len)) < 0 || + (error = git_http_client_send_request( + transport->http_client, &request)) < 0) + goto done; + + if (request.expect_continue && + git_http_client_has_response(transport->http_client)) { + bool complete; + + /* + * If we got a response to an expect/continue, then + * it's something other than a 100 and we should + * deal with the response somehow. + */ + if ((error = git_http_client_read_response(&response, transport->http_client)) < 0 || + (error = handle_response(&complete, stream, &response, true)) < 0) + goto done; } else { - t->parse_buffer.offset = t->parse_buffer.len - buf_size; + stream->state = HTTP_STATE_SENDING_REQUEST; } - data_offset = t->parse_buffer.offset; - - if (gitno_recv(&t->parse_buffer) < 0) - return -1; - - /* This call to http_parser_execute will result in invocations of the - * on_* family of callbacks. The most interesting of these is - * on_body_fill_buffer, which is called when data is ready to be copied - * into the target buffer. We need to marshal the buffer, buf_size, and - * bytes_read parameters to this callback. */ - ctx.t = t; - ctx.s = s; - ctx.buffer = buffer; - ctx.buf_size = buf_size; - ctx.bytes_read = bytes_read; - - /* Set the context, call the parser, then unset the context. */ - t->parser.data = &ctx; - - bytes_parsed = http_parser_execute(&t->parser, - &t->settings, - t->parse_buffer.data + data_offset, - t->parse_buffer.offset - data_offset); - - t->parser.data = NULL; - - /* If there was a handled authentication failure, then parse_error - * will have signaled us that we should replay the request. */ - if (PARSE_ERROR_REPLAY == t->parse_error) { - s->sent_request = 0; - - if ((error = http_connect(t)) < 0) - return error; - - goto replay; - } - - if (t->parse_error == PARSE_ERROR_EXT) { - return t->error; - } - - if (t->parse_error < 0) - return -1; - - if (bytes_parsed != t->parse_buffer.offset - data_offset) { - git_error_set(GIT_ERROR_NET, - "HTTP parser error: %s", - http_errno_description((enum http_errno)t->parser.http_errno)); - return -1; - } + stream->replay_count++; } - return 0; + if (stream->state == HTTP_STATE_NONE) { + git_error_set(GIT_ERROR_HTTP, + "too many redirects or authentication replays"); + error = -1; + goto done; + } + + assert(stream->state == HTTP_STATE_SENDING_REQUEST); + + error = git_http_client_send_body(transport->http_client, buffer, len); + +done: + git_http_response_dispose(&response); + git_net_url_dispose(&url); + return error; } -static int http_stream_write_chunked( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) +/* +* Read from an HTTP transport after it has been written to. This is the +* response from a POST request made by http_stream_write. +*/ +static int http_stream_read_response( + git_smart_subtransport_stream *s, + char *buffer, + size_t buffer_size, + size_t *out_len) { - http_stream *s = (http_stream *)stream; - http_subtransport *t = OWNING_SUBTRANSPORT(s); + http_stream *stream = (http_stream *)s; + http_subtransport *transport = OWNING_SUBTRANSPORT(stream); + git_http_client *client = transport->http_client; + git_http_response response = {0}; + bool complete; + int error; - assert(t->connected); + *out_len = 0; - /* Send the request, if necessary */ - if (!s->sent_request) { - git_buf request = GIT_BUF_INIT; + if (stream->state == HTTP_STATE_SENDING_REQUEST) { + if ((error = git_http_client_read_response(&response, client)) < 0 || + (error = handle_response(&complete, stream, &response, false)) < 0) + goto done; - clear_parser_state(t); - - if (gen_request(&request, s, 0) < 0) - return -1; - - if (git_stream__write_full(t->server.stream, request.ptr, - request.size, 0) < 0) { - git_buf_dispose(&request); - return -1; - } - - git_buf_dispose(&request); - - s->sent_request = 1; + assert(complete); + stream->state = HTTP_STATE_RECEIVING_RESPONSE; } - if (len > CHUNK_SIZE) { - /* Flush, if necessary */ - if (s->chunk_buffer_len > 0) { - if (write_chunk(t->server.stream, - s->chunk_buffer, s->chunk_buffer_len) < 0) - return -1; + error = git_http_client_read_body(client, buffer, buffer_size); - s->chunk_buffer_len = 0; - } - - /* Write chunk directly */ - if (write_chunk(t->server.stream, buffer, len) < 0) - return -1; - } - else { - /* Append as much to the buffer as we can */ - int count = min(CHUNK_SIZE - s->chunk_buffer_len, len); - - if (!s->chunk_buffer) - s->chunk_buffer = git__malloc(CHUNK_SIZE); - - memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count); - s->chunk_buffer_len += count; - buffer += count; - len -= count; - - /* Is the buffer full? If so, then flush */ - if (CHUNK_SIZE == s->chunk_buffer_len) { - if (write_chunk(t->server.stream, - s->chunk_buffer, s->chunk_buffer_len) < 0) - return -1; - - s->chunk_buffer_len = 0; - - if (len > 0) { - memcpy(s->chunk_buffer, buffer, len); - s->chunk_buffer_len = len; - } - } + if (error > 0) { + *out_len = error; + error = 0; } - return 0; -} - -static int http_stream_write_single( - git_smart_subtransport_stream *stream, - const char *buffer, - size_t len) -{ - http_stream *s = (http_stream *)stream; - http_subtransport *t = OWNING_SUBTRANSPORT(s); - git_buf request = GIT_BUF_INIT; - - assert(t->connected); - - if (s->sent_request) { - git_error_set(GIT_ERROR_NET, "subtransport configured for only one write"); - return -1; - } - - clear_parser_state(t); - - if (gen_request(&request, s, len) < 0) - return -1; - - if (git_stream__write_full(t->server.stream, request.ptr, request.size, 0) < 0) - goto on_error; - - if (len && git_stream__write_full(t->server.stream, buffer, len, 0) < 0) - goto on_error; - - git_buf_dispose(&request); - s->sent_request = 1; - - return 0; - -on_error: - git_buf_dispose(&request); - return -1; +done: + git_http_response_dispose(&response); + return error; } static void http_stream_free(git_smart_subtransport_stream *stream) { - http_stream *s = (http_stream *)stream; - - if (s->chunk_buffer) - git__free(s->chunk_buffer); - - if (s->redirect_url) - git__free(s->redirect_url); - + http_stream *s = GIT_CONTAINER_OF(stream, http_stream, parent); git__free(s); } -static int http_stream_alloc(http_subtransport *t, - git_smart_subtransport_stream **stream) +static const http_service *select_service(git_smart_service_t action) { - http_stream *s; + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + return &upload_pack_ls_service; + case GIT_SERVICE_UPLOADPACK: + return &upload_pack_service; + case GIT_SERVICE_RECEIVEPACK_LS: + return &receive_pack_ls_service; + case GIT_SERVICE_RECEIVEPACK: + return &receive_pack_service; + } - if (!stream) - return -1; - - s = git__calloc(sizeof(http_stream), 1); - GIT_ERROR_CHECK_ALLOC(s); - - s->parent.subtransport = &t->parent; - s->parent.read = http_stream_read; - s->parent.write = http_stream_write_single; - s->parent.free = http_stream_free; - - *stream = (git_smart_subtransport_stream *)s; - return 0; -} - -static int http_uploadpack_ls( - http_subtransport *t, - git_smart_subtransport_stream **stream) -{ - http_stream *s; - - if (http_stream_alloc(t, stream) < 0) - return -1; - - s = (http_stream *)*stream; - - s->service = upload_pack_service; - s->service_url = upload_pack_ls_service_url; - s->verb = get_verb; - - return 0; -} - -static int http_uploadpack( - http_subtransport *t, - git_smart_subtransport_stream **stream) -{ - http_stream *s; - - if (http_stream_alloc(t, stream) < 0) - return -1; - - s = (http_stream *)*stream; - - s->service = upload_pack_service; - s->service_url = upload_pack_service_url; - s->verb = post_verb; - - return 0; -} - -static int http_receivepack_ls( - http_subtransport *t, - git_smart_subtransport_stream **stream) -{ - http_stream *s; - - if (http_stream_alloc(t, stream) < 0) - return -1; - - s = (http_stream *)*stream; - - s->service = receive_pack_service; - s->service_url = receive_pack_ls_service_url; - s->verb = get_verb; - - return 0; -} - -static int http_receivepack( - http_subtransport *t, - git_smart_subtransport_stream **stream) -{ - http_stream *s; - - if (http_stream_alloc(t, stream) < 0) - return -1; - - s = (http_stream *)*stream; - - /* Use Transfer-Encoding: chunked for this request */ - s->chunked = 1; - s->parent.write = http_stream_write_chunked; - - s->service = receive_pack_service; - s->service_url = receive_pack_service_url; - s->verb = post_verb; - - return 0; + return NULL; } static int http_action( - git_smart_subtransport_stream **stream, - git_smart_subtransport *subtransport, + git_smart_subtransport_stream **out, + git_smart_subtransport *t, const char *url, git_smart_service_t action) { - http_subtransport *t = (http_subtransport *)subtransport; - int ret; + http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); + http_stream *stream; + const http_service *service; + int error; - assert(stream); + assert(out && t); + + *out = NULL; /* * If we've seen a redirect then preserve the location that we've @@ -1377,121 +649,89 @@ static int http_action( * have redirected us from HTTP->HTTPS and is using an auth mechanism * that would be insecure in plaintext (eg, HTTP Basic). */ - if ((!t->server.url.host || !t->server.url.port || !t->server.url.path) && - (ret = gitno_connection_data_from_url(&t->server.url, url, NULL)) < 0) - return ret; + if (!git_net_url_valid(&transport->server.url) && + (error = git_net_url_parse(&transport->server.url, url)) < 0) + return error; - assert(t->server.url.host && t->server.url.port && t->server.url.path); - - if ((ret = http_connect(t)) < 0) - return ret; - - switch (action) { - case GIT_SERVICE_UPLOADPACK_LS: - return http_uploadpack_ls(t, stream); - - case GIT_SERVICE_UPLOADPACK: - return http_uploadpack(t, stream); - - case GIT_SERVICE_RECEIVEPACK_LS: - return http_receivepack_ls(t, stream); - - case GIT_SERVICE_RECEIVEPACK: - return http_receivepack(t, stream); + if ((service = select_service(action)) == NULL) { + git_error_set(GIT_ERROR_HTTP, "invalid action"); + return -1; } - *stream = NULL; - return -1; + stream = git__calloc(sizeof(http_stream), 1); + GIT_ERROR_CHECK_ALLOC(stream); + + if (!transport->http_client) { + git_http_client_options opts = {0}; + + opts.server_certificate_check_cb = transport->owner->certificate_check_cb; + opts.server_certificate_check_payload = transport->owner->message_cb_payload; + opts.proxy_certificate_check_cb = transport->owner->proxy.certificate_check; + opts.proxy_certificate_check_payload = transport->owner->proxy.payload; + + if (git_http_client_new(&transport->http_client, &opts) < 0) + return -1; + } + + stream->service = service; + stream->parent.subtransport = &transport->parent; + + if (service->method == GIT_HTTP_METHOD_GET) { + stream->parent.read = http_stream_read; + } else { + stream->parent.write = http_stream_write; + stream->parent.read = http_stream_read_response; + } + + stream->parent.free = http_stream_free; + + *out = (git_smart_subtransport_stream *)stream; + return 0; } -static void free_auth_contexts(git_vector *contexts) +static int http_close(git_smart_subtransport *t) { - git_http_auth_context *context; - size_t i; + http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); - git_vector_foreach(contexts, i, context) { - if (context->free) - context->free(context); - } + free_cred(&transport->server.cred); + free_cred(&transport->proxy.cred); - git_vector_clear(contexts); -} + transport->server.url_cred_presented = false; + transport->proxy.url_cred_presented = false; -static int http_close(git_smart_subtransport *subtransport) -{ - http_subtransport *t = (http_subtransport *) subtransport; - - clear_parser_state(t); - - t->connected = 0; - - if (t->server.stream) { - git_stream_close(t->server.stream); - git_stream_free(t->server.stream); - t->server.stream = NULL; - } - - if (t->proxy.stream) { - git_stream_close(t->proxy.stream); - git_stream_free(t->proxy.stream); - t->proxy.stream = NULL; - } - - free_cred(&t->server.cred); - free_cred(&t->server.url_cred); - free_cred(&t->proxy.cred); - free_cred(&t->proxy.url_cred); - - free_auth_contexts(&t->server.auth_contexts); - free_auth_contexts(&t->proxy.auth_contexts); - - gitno_connection_data_free_ptrs(&t->server.url); - memset(&t->server.url, 0x0, sizeof(gitno_connection_data)); - - gitno_connection_data_free_ptrs(&t->proxy.url); - memset(&t->proxy.url, 0x0, sizeof(gitno_connection_data)); - - git__free(t->proxy_url); - t->proxy_url = NULL; + git_net_url_dispose(&transport->server.url); + git_net_url_dispose(&transport->proxy.url); return 0; } -static void http_free(git_smart_subtransport *subtransport) +static void http_free(git_smart_subtransport *t) { - http_subtransport *t = (http_subtransport *) subtransport; + http_subtransport *transport = GIT_CONTAINER_OF(t, http_subtransport, parent); - http_close(subtransport); + git_http_client_free(transport->http_client); - git_vector_free(&t->server.auth_contexts); - git_vector_free(&t->proxy.auth_contexts); - git__free(t); + http_close(t); + git__free(transport); } int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param) { - http_subtransport *t; + http_subtransport *transport; GIT_UNUSED(param); - if (!out) - return -1; + assert(out); - t = git__calloc(sizeof(http_subtransport), 1); - GIT_ERROR_CHECK_ALLOC(t); + transport = git__calloc(sizeof(http_subtransport), 1); + GIT_ERROR_CHECK_ALLOC(transport); - t->owner = (transport_smart *)owner; - t->parent.action = http_action; - t->parent.close = http_close; - t->parent.free = http_free; + transport->owner = (transport_smart *)owner; + transport->parent.action = http_action; + transport->parent.close = http_close; + transport->parent.free = http_free; - t->settings.on_header_field = on_header_field; - t->settings.on_header_value = on_header_value; - t->settings.on_headers_complete = on_headers_complete; - t->settings.on_body = on_body_fill_buffer; - t->settings.on_message_complete = on_message_complete; - - *out = (git_smart_subtransport *) t; + *out = (git_smart_subtransport *) transport; return 0; } diff --git a/src/transports/http.h b/src/transports/http.h index b09475755..c02109cec 100644 --- a/src/transports/http.h +++ b/src/transports/http.h @@ -9,8 +9,11 @@ #define INCLUDE_transports_http_h__ #include "buffer.h" +#include "httpclient.h" -#define GIT_HTTP_REPLAY_MAX 7 +#define GIT_HTTP_REPLAY_MAX 15 + +extern bool git_http__expect_continue; GIT_INLINE(int) git_http__user_agent(git_buf *buf) { diff --git a/src/transports/httpclient.c b/src/transports/httpclient.c new file mode 100644 index 000000000..010baa604 --- /dev/null +++ b/src/transports/httpclient.c @@ -0,0 +1,1549 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "git2.h" +#include "http_parser.h" +#include "vector.h" +#include "trace.h" +#include "global.h" +#include "httpclient.h" +#include "http.h" +#include "auth.h" +#include "auth_negotiate.h" +#include "auth_ntlm.h" +#include "git2/sys/credential.h" +#include "net.h" +#include "stream.h" +#include "streams/socket.h" +#include "streams/tls.h" +#include "auth.h" + +static git_http_auth_scheme auth_schemes[] = { + { GIT_HTTP_AUTH_NEGOTIATE, "Negotiate", GIT_CREDENTIAL_DEFAULT, git_http_auth_negotiate }, + { GIT_HTTP_AUTH_NTLM, "NTLM", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_ntlm }, + { GIT_HTTP_AUTH_BASIC, "Basic", GIT_CREDENTIAL_USERPASS_PLAINTEXT, git_http_auth_basic }, +}; + +/* + * Use a 16kb read buffer to match the maximum size of a TLS packet. This + * is critical for compatibility with SecureTransport, which will always do + * a network read on every call, even if it has data buffered to return to + * you. That buffered data may be the _end_ of a keep-alive response, so + * if SecureTransport performs another network read, it will wait until the + * server ultimately times out before it returns that buffered data to you. + * Since SecureTransport only reads a single TLS packet at a time, by + * calling it with a read buffer that is the maximum size of a TLS packet, + * we ensure that it will never buffer. + */ +#define GIT_READ_BUFFER_SIZE (16 * 1024) + +typedef struct { + git_net_url url; + git_stream *stream; + + git_vector auth_challenges; + git_http_auth_context *auth_context; +} git_http_server; + +typedef enum { + PROXY = 1, + SERVER +} git_http_server_t; + +typedef enum { + NONE = 0, + SENDING_REQUEST, + SENDING_BODY, + SENT_REQUEST, + HAS_EARLY_RESPONSE, + READING_RESPONSE, + READING_BODY, + DONE +} http_client_state; + +/* Parser state */ +typedef enum { + PARSE_HEADER_NONE = 0, + PARSE_HEADER_NAME, + PARSE_HEADER_VALUE, + PARSE_HEADER_COMPLETE +} parse_header_state; + +typedef enum { + PARSE_STATUS_OK, + PARSE_STATUS_NO_OUTPUT, + PARSE_STATUS_ERROR +} parse_status; + +typedef struct { + git_http_client *client; + git_http_response *response; + + /* Temporary buffers to avoid extra mallocs */ + git_buf parse_header_name; + git_buf parse_header_value; + + /* Parser state */ + int error; + parse_status parse_status; + + /* Headers parsing */ + parse_header_state parse_header_state; + + /* Body parsing */ + char *output_buf; /* Caller's output buffer */ + size_t output_size; /* Size of caller's output buffer */ + size_t output_written; /* Bytes we've written to output buffer */ +} http_parser_context; + +/* HTTP client connection */ +struct git_http_client { + git_http_client_options opts; + + /* Are we writing to the proxy or server, and state of the client. */ + git_http_server_t current_server; + http_client_state state; + + http_parser parser; + + git_http_server server; + git_http_server proxy; + + unsigned request_count; + unsigned connected : 1, + proxy_connected : 1, + keepalive : 1, + request_chunked : 1; + + /* Temporary buffers to avoid extra mallocs */ + git_buf request_msg; + git_buf read_buf; + + /* A subset of information from the request */ + size_t request_body_len, + request_body_remain; + + /* + * When state == HAS_EARLY_RESPONSE, the response of our proxy + * that we have buffered and will deliver during read_response. + */ + git_http_response early_response; +}; + +bool git_http_response_is_redirect(git_http_response *response) +{ + return (response->status == GIT_HTTP_MOVED_PERMANENTLY || + response->status == GIT_HTTP_FOUND || + response->status == GIT_HTTP_SEE_OTHER || + response->status == GIT_HTTP_TEMPORARY_REDIRECT || + response->status == GIT_HTTP_PERMANENT_REDIRECT); +} + +void git_http_response_dispose(git_http_response *response) +{ + assert(response); + + git__free(response->content_type); + git__free(response->location); + + memset(response, 0, sizeof(git_http_response)); +} + +static int on_header_complete(http_parser *parser) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + git_http_client *client = ctx->client; + git_http_response *response = ctx->response; + + git_buf *name = &ctx->parse_header_name; + git_buf *value = &ctx->parse_header_value; + + if (!strcasecmp("Content-Type", name->ptr)) { + if (response->content_type) { + git_error_set(GIT_ERROR_HTTP, + "multiple content-type headers"); + return -1; + } + + response->content_type = + git__strndup(value->ptr, value->size); + GIT_ERROR_CHECK_ALLOC(ctx->response->content_type); + } else if (!strcasecmp("Content-Length", name->ptr)) { + int64_t len; + + if (response->content_length) { + git_error_set(GIT_ERROR_HTTP, + "multiple content-length headers"); + return -1; + } + + if (git__strntol64(&len, value->ptr, value->size, + NULL, 10) < 0 || len < 0) { + git_error_set(GIT_ERROR_HTTP, + "invalid content-length"); + return -1; + } + + response->content_length = (size_t)len; + } else if (!strcasecmp("Transfer-Encoding", name->ptr) && + !strcasecmp("chunked", value->ptr)) { + ctx->response->chunked = 1; + } else if (!strcasecmp("Proxy-Authenticate", git_buf_cstr(name))) { + char *dup = git__strndup(value->ptr, value->size); + GIT_ERROR_CHECK_ALLOC(dup); + + if (git_vector_insert(&client->proxy.auth_challenges, dup) < 0) + return -1; + } else if (!strcasecmp("WWW-Authenticate", name->ptr)) { + char *dup = git__strndup(value->ptr, value->size); + GIT_ERROR_CHECK_ALLOC(dup); + + if (git_vector_insert(&client->server.auth_challenges, dup) < 0) + return -1; + } else if (!strcasecmp("Location", name->ptr)) { + if (response->location) { + git_error_set(GIT_ERROR_HTTP, + "multiple location headers"); + return -1; + } + + response->location = git__strndup(value->ptr, value->size); + GIT_ERROR_CHECK_ALLOC(response->location); + } + + return 0; +} + +static int on_header_field(http_parser *parser, const char *str, size_t len) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + + switch (ctx->parse_header_state) { + /* + * We last saw a header value, process the name/value pair and + * get ready to handle this new name. + */ + case PARSE_HEADER_VALUE: + if (on_header_complete(parser) < 0) + return ctx->parse_status = PARSE_STATUS_ERROR; + + git_buf_clear(&ctx->parse_header_name); + git_buf_clear(&ctx->parse_header_value); + /* Fall through */ + + case PARSE_HEADER_NONE: + case PARSE_HEADER_NAME: + ctx->parse_header_state = PARSE_HEADER_NAME; + + if (git_buf_put(&ctx->parse_header_name, str, len) < 0) + return ctx->parse_status = PARSE_STATUS_ERROR; + + break; + + default: + git_error_set(GIT_ERROR_HTTP, + "header name seen at unexpected time"); + return ctx->parse_status = PARSE_STATUS_ERROR; + } + + return 0; +} + +static int on_header_value(http_parser *parser, const char *str, size_t len) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + + switch (ctx->parse_header_state) { + case PARSE_HEADER_NAME: + case PARSE_HEADER_VALUE: + ctx->parse_header_state = PARSE_HEADER_VALUE; + + if (git_buf_put(&ctx->parse_header_value, str, len) < 0) + return ctx->parse_status = PARSE_STATUS_ERROR; + + break; + + default: + git_error_set(GIT_ERROR_HTTP, + "header value seen at unexpected time"); + return ctx->parse_status = PARSE_STATUS_ERROR; + } + + return 0; +} + +GIT_INLINE(bool) challenge_matches_scheme( + const char *challenge, + git_http_auth_scheme *scheme) +{ + const char *scheme_name = scheme->name; + size_t scheme_len = strlen(scheme_name); + + if (!strncasecmp(challenge, scheme_name, scheme_len) && + (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' ')) + return true; + + return false; +} + +static git_http_auth_scheme *scheme_for_challenge(const char *challenge) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) { + if (challenge_matches_scheme(challenge, &auth_schemes[i])) + return &auth_schemes[i]; + } + + return NULL; +} + +GIT_INLINE(void) collect_authinfo( + unsigned int *schemetypes, + unsigned int *credtypes, + git_vector *challenges) +{ + git_http_auth_scheme *scheme; + const char *challenge; + size_t i; + + *schemetypes = 0; + *credtypes = 0; + + git_vector_foreach(challenges, i, challenge) { + if ((scheme = scheme_for_challenge(challenge)) != NULL) { + *schemetypes |= scheme->type; + *credtypes |= scheme->credtypes; + } + } +} + +static int resend_needed(git_http_client *client, git_http_response *response) +{ + git_http_auth_context *auth_context; + + if (response->status == GIT_HTTP_STATUS_UNAUTHORIZED && + (auth_context = client->server.auth_context) && + auth_context->is_complete && + !auth_context->is_complete(auth_context)) + return 1; + + if (response->status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED && + (auth_context = client->proxy.auth_context) && + auth_context->is_complete && + !auth_context->is_complete(auth_context)) + return 1; + + return 0; +} + +static int on_headers_complete(http_parser *parser) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + + /* Finalize the last seen header */ + switch (ctx->parse_header_state) { + case PARSE_HEADER_VALUE: + if (on_header_complete(parser) < 0) + return ctx->parse_status = PARSE_STATUS_ERROR; + + /* Fall through */ + + case PARSE_HEADER_NONE: + ctx->parse_header_state = PARSE_HEADER_COMPLETE; + break; + + default: + git_error_set(GIT_ERROR_HTTP, + "header completion at unexpected time"); + return ctx->parse_status = PARSE_STATUS_ERROR; + } + + ctx->response->status = parser->status_code; + ctx->client->keepalive = http_should_keep_alive(parser); + + /* Prepare for authentication */ + collect_authinfo(&ctx->response->server_auth_schemetypes, + &ctx->response->server_auth_credtypes, + &ctx->client->server.auth_challenges); + collect_authinfo(&ctx->response->proxy_auth_schemetypes, + &ctx->response->proxy_auth_credtypes, + &ctx->client->proxy.auth_challenges); + + ctx->response->resend_credentials = resend_needed(ctx->client, + ctx->response); + + /* Stop parsing. */ + http_parser_pause(parser, 1); + + if (ctx->response->content_type || ctx->response->chunked) + ctx->client->state = READING_BODY; + else + ctx->client->state = DONE; + + return 0; +} + +static int on_body(http_parser *parser, const char *buf, size_t len) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + size_t max_len; + + /* Saw data when we expected not to (eg, in consume_response_body) */ + if (ctx->output_buf == NULL && ctx->output_size == 0) { + ctx->parse_status = PARSE_STATUS_NO_OUTPUT; + return 0; + } + + assert(ctx->output_size >= ctx->output_written); + + max_len = min(ctx->output_size - ctx->output_written, len); + max_len = min(max_len, INT_MAX); + + memcpy(ctx->output_buf + ctx->output_written, buf, max_len); + ctx->output_written += max_len; + + return 0; +} + +static int on_message_complete(http_parser *parser) +{ + http_parser_context *ctx = (http_parser_context *) parser->data; + + ctx->client->state = DONE; + return 0; +} + +GIT_INLINE(int) stream_write( + git_http_server *server, + const char *data, + size_t len) +{ + git_trace(GIT_TRACE_TRACE, + "Sending request:\n%.*s", (int)len, data); + + return git_stream__write_full(server->stream, data, len, 0); +} + +GIT_INLINE(int) client_write_request(git_http_client *client) +{ + git_stream *stream = client->current_server == PROXY ? + client->proxy.stream : client->server.stream; + + git_trace(GIT_TRACE_TRACE, + "Sending request:\n%.*s", + (int)client->request_msg.size, client->request_msg.ptr); + + return git_stream__write_full(stream, + client->request_msg.ptr, + client->request_msg.size, + 0); +} + +const char *name_for_method(git_http_method method) +{ + switch (method) { + case GIT_HTTP_METHOD_GET: + return "GET"; + case GIT_HTTP_METHOD_POST: + return "POST"; + case GIT_HTTP_METHOD_CONNECT: + return "CONNECT"; + } + + return NULL; +} + +/* + * Find the scheme that is suitable for the given credentials, based on the + * server's auth challenges. + */ +static bool best_scheme_and_challenge( + git_http_auth_scheme **scheme_out, + const char **challenge_out, + git_vector *challenges, + git_credential *credentials) +{ + const char *challenge; + size_t i, j; + + for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) { + git_vector_foreach(challenges, j, challenge) { + git_http_auth_scheme *scheme = &auth_schemes[i]; + + if (challenge_matches_scheme(challenge, scheme) && + (scheme->credtypes & credentials->credtype)) { + *scheme_out = scheme; + *challenge_out = challenge; + return true; + } + } + } + + return false; +} + +/* + * Find the challenge from the server for our current auth context. + */ +static const char *challenge_for_context( + git_vector *challenges, + git_http_auth_context *auth_ctx) +{ + const char *challenge; + size_t i, j; + + for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) { + if (auth_schemes[i].type == auth_ctx->type) { + git_http_auth_scheme *scheme = &auth_schemes[i]; + + git_vector_foreach(challenges, j, challenge) { + if (challenge_matches_scheme(challenge, scheme)) + return challenge; + } + } + } + + return NULL; +} + +static const char *init_auth_context( + git_http_server *server, + git_vector *challenges, + git_credential *credentials) +{ + git_http_auth_scheme *scheme; + const char *challenge; + int error; + + if (!best_scheme_and_challenge(&scheme, &challenge, challenges, credentials)) { + git_error_set(GIT_ERROR_HTTP, "could not find appropriate mechanism for credentials"); + return NULL; + } + + error = scheme->init_context(&server->auth_context, &server->url); + + if (error == GIT_PASSTHROUGH) { + git_error_set(GIT_ERROR_HTTP, "'%s' authentication is not supported", scheme->name); + return NULL; + } + + return challenge; +} + +static void free_auth_context(git_http_server *server) +{ + if (!server->auth_context) + return; + + if (server->auth_context->free) + server->auth_context->free(server->auth_context); + + server->auth_context = NULL; +} + +static int apply_credentials( + git_buf *buf, + git_http_server *server, + const char *header_name, + git_credential *credentials) +{ + git_http_auth_context *auth = server->auth_context; + git_vector *challenges = &server->auth_challenges; + const char *challenge; + git_buf token = GIT_BUF_INIT; + int error = 0; + + /* We've started a new request without creds; free the context. */ + if (auth && !credentials) { + free_auth_context(server); + return 0; + } + + /* We haven't authenticated, nor were we asked to. Nothing to do. */ + if (!auth && !git_vector_length(challenges)) + return 0; + + if (!auth) { + challenge = init_auth_context(server, challenges, credentials); + auth = server->auth_context; + + if (!challenge || !auth) { + error = -1; + goto done; + } + } else if (auth->set_challenge) { + challenge = challenge_for_context(challenges, auth); + } + + if (auth->set_challenge && challenge && + (error = auth->set_challenge(auth, challenge)) < 0) + goto done; + + if ((error = auth->next_token(&token, auth, credentials)) < 0) + goto done; + + if (auth->is_complete && auth->is_complete(auth)) { + /* + * If we're done with an auth mechanism with connection affinity, + * we don't need to send any more headers and can dispose the context. + */ + if (auth->connection_affinity) + free_auth_context(server); + } else if (!token.size) { + git_error_set(GIT_ERROR_HTTP, "failed to respond to authentication challenge"); + error = -1; + goto done; + } + + if (token.size > 0) + error = git_buf_printf(buf, "%s: %s\r\n", header_name, token.ptr); + +done: + git_buf_dispose(&token); + return error; +} + +GIT_INLINE(int) apply_server_credentials( + git_buf *buf, + git_http_client *client, + git_http_request *request) +{ + return apply_credentials(buf, + &client->server, + "Authorization", + request->credentials); +} + +GIT_INLINE(int) apply_proxy_credentials( + git_buf *buf, + git_http_client *client, + git_http_request *request) +{ + return apply_credentials(buf, + &client->proxy, + "Proxy-Authorization", + request->proxy_credentials); +} + +static int generate_connect_request( + git_http_client *client, + git_http_request *request) +{ + git_buf *buf; + int error; + + git_buf_clear(&client->request_msg); + buf = &client->request_msg; + + git_buf_printf(buf, "CONNECT %s:%s HTTP/1.1\r\n", + client->server.url.host, client->server.url.port); + + git_buf_puts(buf, "User-Agent: "); + git_http__user_agent(buf); + git_buf_puts(buf, "\r\n"); + + git_buf_printf(buf, "Host: %s\r\n", client->proxy.url.host); + + if ((error = apply_proxy_credentials(buf, client, request) < 0)) + return -1; + + git_buf_puts(buf, "\r\n"); + + return git_buf_oom(buf) ? -1 : 0; +} + +static int generate_request( + git_http_client *client, + git_http_request *request) +{ + git_buf *buf; + size_t i; + int error; + + assert(client && request); + + git_buf_clear(&client->request_msg); + buf = &client->request_msg; + + /* GET|POST path HTTP/1.1 */ + git_buf_puts(buf, name_for_method(request->method)); + git_buf_putc(buf, ' '); + + if (request->proxy && strcmp(request->url->scheme, "https")) + git_net_url_fmt(buf, request->url); + else + git_net_url_fmt_path(buf, request->url); + + git_buf_puts(buf, " HTTP/1.1\r\n"); + + git_buf_puts(buf, "User-Agent: "); + git_http__user_agent(buf); + git_buf_puts(buf, "\r\n"); + + git_buf_printf(buf, "Host: %s", request->url->host); + + if (!git_net_url_is_default_port(request->url)) + git_buf_printf(buf, ":%s", request->url->port); + + git_buf_puts(buf, "\r\n"); + + if (request->accept) + git_buf_printf(buf, "Accept: %s\r\n", request->accept); + else + git_buf_puts(buf, "Accept: */*\r\n"); + + if (request->content_type) + git_buf_printf(buf, "Content-Type: %s\r\n", + request->content_type); + + if (request->chunked) + git_buf_puts(buf, "Transfer-Encoding: chunked\r\n"); + + if (request->content_length > 0) + git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", + request->content_length); + + if (request->expect_continue) + git_buf_printf(buf, "Expect: 100-continue\r\n"); + + if ((error = apply_server_credentials(buf, client, request)) < 0 || + (error = apply_proxy_credentials(buf, client, request)) < 0) + return error; + + if (request->custom_headers) { + for (i = 0; i < request->custom_headers->count; i++) { + const char *hdr = request->custom_headers->strings[i]; + + if (hdr) + git_buf_printf(buf, "%s\r\n", hdr); + } + } + + git_buf_puts(buf, "\r\n"); + + if (git_buf_oom(buf)) + return -1; + + return 0; +} + +static int check_certificate( + git_stream *stream, + git_net_url *url, + int is_valid, + git_transport_certificate_check_cb cert_cb, + void *cert_cb_payload) +{ + git_cert *cert; + git_error_state last_error = {0}; + int error; + + if ((error = git_stream_certificate(&cert, stream)) < 0) + return error; + + git_error_state_capture(&last_error, GIT_ECERTIFICATE); + + error = cert_cb(cert, is_valid, url->host, cert_cb_payload); + + if (error == GIT_PASSTHROUGH && !is_valid) + return git_error_state_restore(&last_error); + else if (error == GIT_PASSTHROUGH) + error = 0; + else if (error && !git_error_last()) + git_error_set(GIT_ERROR_HTTP, + "user rejected certificate for %s", url->host); + + git_error_state_free(&last_error); + return error; +} + +static int server_connect_stream( + git_http_server *server, + git_transport_certificate_check_cb cert_cb, + void *cb_payload) +{ + int error; + + GIT_ERROR_CHECK_VERSION(server->stream, GIT_STREAM_VERSION, "git_stream"); + + error = git_stream_connect(server->stream); + + if (error && error != GIT_ECERTIFICATE) + return error; + + if (git_stream_is_encrypted(server->stream) && cert_cb != NULL) + error = check_certificate(server->stream, &server->url, !error, + cert_cb, cb_payload); + + return error; +} + +static void reset_auth_connection(git_http_server *server) +{ + /* + * If we've authenticated and we're doing "normal" + * authentication with a request affinity (Basic, Digest) + * then we want to _keep_ our context, since authentication + * survives even through non-keep-alive connections. If + * we've authenticated and we're doing connection-based + * authentication (NTLM, Negotiate) - indicated by the presence + * of an `is_complete` callback - then we need to restart + * authentication on a new connection. + */ + + if (server->auth_context && + server->auth_context->connection_affinity) + free_auth_context(server); +} + +/* + * Updates the server data structure with the new URL; returns 1 if the server + * has changed and we need to reconnect, returns 0 otherwise. + */ +GIT_INLINE(int) server_setup_from_url( + git_http_server *server, + git_net_url *url) +{ + if (!server->url.scheme || strcmp(server->url.scheme, url->scheme) || + !server->url.host || strcmp(server->url.host, url->host) || + !server->url.port || strcmp(server->url.port, url->port)) { + git__free(server->url.scheme); + git__free(server->url.host); + git__free(server->url.port); + + server->url.scheme = git__strdup(url->scheme); + GIT_ERROR_CHECK_ALLOC(server->url.scheme); + + server->url.host = git__strdup(url->host); + GIT_ERROR_CHECK_ALLOC(server->url.host); + + server->url.port = git__strdup(url->port); + GIT_ERROR_CHECK_ALLOC(server->url.port); + + return 1; + } + + return 0; +} + +static void reset_parser(git_http_client *client) +{ + http_parser_init(&client->parser, HTTP_RESPONSE); +} + +static int setup_hosts( + git_http_client *client, + git_http_request *request) +{ + int ret, diff = 0; + + assert(client && request && request->url); + + if ((ret = server_setup_from_url(&client->server, request->url)) < 0) + return ret; + + diff |= ret; + + if (request->proxy && + (ret = server_setup_from_url(&client->proxy, request->proxy)) < 0) + return ret; + + diff |= ret; + + if (diff) { + free_auth_context(&client->server); + free_auth_context(&client->proxy); + + client->connected = 0; + } + + return 0; +} + +GIT_INLINE(int) server_create_stream(git_http_server *server) +{ + git_net_url *url = &server->url; + + if (strcasecmp(url->scheme, "https") == 0) + return git_tls_stream_new(&server->stream, url->host, url->port); + else if (strcasecmp(url->scheme, "http") == 0) + return git_socket_stream_new(&server->stream, url->host, url->port); + + git_error_set(GIT_ERROR_HTTP, "unknown http scheme '%s'", url->scheme); + return -1; +} + +GIT_INLINE(void) save_early_response( + git_http_client *client, + git_http_response *response) +{ + /* Buffer the response so we can return it in read_response */ + client->state = HAS_EARLY_RESPONSE; + + memcpy(&client->early_response, response, sizeof(git_http_response)); + memset(response, 0, sizeof(git_http_response)); +} + +static int proxy_connect( + git_http_client *client, + git_http_request *request) +{ + git_http_response response = {0}; + int error; + + if (!client->proxy_connected || !client->keepalive) { + git_trace(GIT_TRACE_DEBUG, "Connecting to proxy %s:%s", + client->proxy.url.host, client->proxy.url.port); + + if ((error = server_create_stream(&client->proxy)) < 0 || + (error = server_connect_stream(&client->proxy, + client->opts.proxy_certificate_check_cb, + client->opts.proxy_certificate_check_payload)) < 0) + goto done; + + client->proxy_connected = 1; + } + + client->current_server = PROXY; + client->state = SENDING_REQUEST; + + if ((error = generate_connect_request(client, request)) < 0 || + (error = client_write_request(client)) < 0) + goto done; + + client->state = SENT_REQUEST; + + if ((error = git_http_client_read_response(&response, client)) < 0 || + (error = git_http_client_skip_body(client)) < 0) + goto done; + + assert(client->state == DONE); + + if (response.status == GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { + save_early_response(client, &response); + + error = GIT_RETRY; + goto done; + } else if (response.status != GIT_HTTP_STATUS_OK) { + git_error_set(GIT_ERROR_HTTP, "proxy returned unexpected status: %d", response.status); + error = -1; + goto done; + } + + reset_parser(client); + client->state = NONE; + +done: + git_http_response_dispose(&response); + return error; +} + +static int server_connect(git_http_client *client) +{ + git_net_url *url = &client->server.url; + git_transport_certificate_check_cb cert_cb; + void *cert_payload; + int error; + + client->current_server = SERVER; + + if (client->proxy.stream) + error = git_tls_stream_wrap(&client->server.stream, client->proxy.stream, url->host); + else + error = server_create_stream(&client->server); + + if (error < 0) + goto done; + + cert_cb = client->opts.server_certificate_check_cb; + cert_payload = client->opts.server_certificate_check_payload; + + error = server_connect_stream(&client->server, cert_cb, cert_payload); + +done: + return error; +} + +GIT_INLINE(void) close_stream(git_http_server *server) +{ + if (server->stream) { + git_stream_close(server->stream); + git_stream_free(server->stream); + server->stream = NULL; + } +} + +static int http_client_connect( + git_http_client *client, + git_http_request *request) +{ + bool use_proxy = false; + int error; + + if ((error = setup_hosts(client, request)) < 0) + goto on_error; + + /* We're connected to our destination server; no need to reconnect */ + if (client->connected && client->keepalive && + (client->state == NONE || client->state == DONE)) + return 0; + + client->connected = 0; + client->request_count = 0; + + close_stream(&client->server); + reset_auth_connection(&client->server); + + reset_parser(client); + + /* Reconnect to the proxy if necessary. */ + use_proxy = client->proxy.url.host && + !strcmp(client->server.url.scheme, "https"); + + if (use_proxy) { + if (!client->proxy_connected || !client->keepalive || + (client->state != NONE && client->state != DONE)) { + close_stream(&client->proxy); + reset_auth_connection(&client->proxy); + + client->proxy_connected = 0; + } + + if ((error = proxy_connect(client, request)) < 0) + goto on_error; + } + + git_trace(GIT_TRACE_DEBUG, "Connecting to remote %s:%s", + client->server.url.host, client->server.url.port); + + if ((error = server_connect(client)) < 0) + goto on_error; + + client->connected = 1; + return error; + +on_error: + if (error != GIT_RETRY) + close_stream(&client->proxy); + + close_stream(&client->server); + return error; +} + +GIT_INLINE(int) client_read(git_http_client *client) +{ + http_parser_context *parser_context = client->parser.data; + git_stream *stream; + char *buf = client->read_buf.ptr + client->read_buf.size; + size_t max_len; + ssize_t read_len; + + stream = client->current_server == PROXY ? + client->proxy.stream : client->server.stream; + + /* + * We use a git_buf for convenience, but statically allocate it and + * don't resize. Limit our consumption to INT_MAX since calling + * functions use an int return type to return number of bytes read. + */ + max_len = client->read_buf.asize - client->read_buf.size; + max_len = min(max_len, INT_MAX); + + if (parser_context->output_size) + max_len = min(max_len, parser_context->output_size); + + if (max_len == 0) { + git_error_set(GIT_ERROR_HTTP, "no room in output buffer"); + return -1; + } + + read_len = git_stream_read(stream, buf, max_len); + + if (read_len >= 0) { + client->read_buf.size += read_len; + + git_trace(GIT_TRACE_TRACE, "Received:\n%.*s", + (int)read_len, buf); + } + + return (int)read_len; +} + +static bool parser_settings_initialized; +static http_parser_settings parser_settings; + +GIT_INLINE(http_parser_settings *) http_client_parser_settings(void) +{ + if (!parser_settings_initialized) { + parser_settings.on_header_field = on_header_field; + parser_settings.on_header_value = on_header_value; + parser_settings.on_headers_complete = on_headers_complete; + parser_settings.on_body = on_body; + parser_settings.on_message_complete = on_message_complete; + + parser_settings_initialized = true; + } + + return &parser_settings; +} + +GIT_INLINE(int) client_read_and_parse(git_http_client *client) +{ + http_parser *parser = &client->parser; + http_parser_context *ctx = (http_parser_context *) parser->data; + unsigned char http_errno; + int read_len; + size_t parsed_len; + + /* + * If we have data in our read buffer, that means we stopped early + * when parsing headers. Use the data in the read buffer instead of + * reading more from the socket. + */ + if (!client->read_buf.size && (read_len = client_read(client)) < 0) + return read_len; + + parsed_len = http_parser_execute(parser, + http_client_parser_settings(), + client->read_buf.ptr, + client->read_buf.size); + http_errno = client->parser.http_errno; + + if (parsed_len > INT_MAX) { + git_error_set(GIT_ERROR_HTTP, "unexpectedly large parse"); + return -1; + } + + if (parser->upgrade) { + git_error_set(GIT_ERROR_HTTP, "server requested upgrade"); + return -1; + } + + if (ctx->parse_status == PARSE_STATUS_ERROR) { + client->connected = 0; + return ctx->error ? ctx->error : -1; + } + + /* + * If we finished reading the headers or body, we paused parsing. + * Otherwise the parser will start filling the body, or even parse + * a new response if the server pipelined us multiple responses. + * (This can happen in response to an expect/continue request, + * where the server gives you a 100 and 200 simultaneously.) + */ + if (http_errno == HPE_PAUSED) { + /* + * http-parser has a "feature" where it will not deliver the + * final byte when paused in a callback. Consume that byte. + * https://github.com/nodejs/http-parser/issues/97 + */ + assert(client->read_buf.size > parsed_len); + + http_parser_pause(parser, 0); + + parsed_len += http_parser_execute(parser, + http_client_parser_settings(), + client->read_buf.ptr + parsed_len, + 1); + } + + /* Most failures will be reported in http_errno */ + else if (parser->http_errno != HPE_OK) { + git_error_set(GIT_ERROR_HTTP, "http parser error: %s", + http_errno_description(http_errno)); + return -1; + } + + /* Otherwise we should have consumed the entire buffer. */ + else if (parsed_len != client->read_buf.size) { + git_error_set(GIT_ERROR_HTTP, + "http parser did not consume entire buffer: %s", + http_errno_description(http_errno)); + return -1; + } + + /* recv returned 0, the server hung up on us */ + else if (!parsed_len) { + git_error_set(GIT_ERROR_HTTP, "unexpected EOF"); + return -1; + } + + git_buf_consume_bytes(&client->read_buf, parsed_len); + + return (int)parsed_len; +} + +/* + * See if we've consumed the entire response body. If the client was + * reading the body but did not consume it entirely, it's possible that + * they knew that the stream had finished (in a git response, seeing a + * final flush) and stopped reading. But if the response was chunked, + * we may have not consumed the final chunk marker. Consume it to + * ensure that we don't have it waiting in our socket. If there's + * more than just a chunk marker, close the connection. + */ +static void complete_response_body(git_http_client *client) +{ + http_parser_context parser_context = {0}; + + /* If we're not keeping alive, don't bother. */ + if (!client->keepalive) { + client->connected = 0; + goto done; + } + + parser_context.client = client; + client->parser.data = &parser_context; + + /* If there was an error, just close the connection. */ + if (client_read_and_parse(client) < 0 || + parser_context.error != HPE_OK || + (parser_context.parse_status != PARSE_STATUS_OK && + parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) { + git_error_clear(); + client->connected = 0; + } + +done: + git_buf_clear(&client->read_buf); +} + +int git_http_client_send_request( + git_http_client *client, + git_http_request *request) +{ + git_http_response response = {0}; + int error = -1; + + assert(client && request); + + /* If the client did not finish reading, clean up the stream. */ + if (client->state == READING_BODY) + complete_response_body(client); + + /* If we're waiting for proxy auth, don't sending more requests. */ + if (client->state == HAS_EARLY_RESPONSE) + return 0; + + if (git_trace_level() >= GIT_TRACE_DEBUG) { + git_buf url = GIT_BUF_INIT; + git_net_url_fmt(&url, request->url); + git_trace(GIT_TRACE_DEBUG, "Sending %s request to %s", + name_for_method(request->method), + url.ptr ? url.ptr : ""); + git_buf_dispose(&url); + } + + if ((error = http_client_connect(client, request)) < 0 || + (error = generate_request(client, request)) < 0 || + (error = client_write_request(client)) < 0) + goto done; + + client->state = SENT_REQUEST; + + if (request->expect_continue) { + if ((error = git_http_client_read_response(&response, client)) < 0 || + (error = git_http_client_skip_body(client)) < 0) + goto done; + + error = 0; + + if (response.status != GIT_HTTP_STATUS_CONTINUE) { + save_early_response(client, &response); + goto done; + } + } + + if (request->content_length || request->chunked) { + client->state = SENDING_BODY; + client->request_body_len = request->content_length; + client->request_body_remain = request->content_length; + client->request_chunked = request->chunked; + } + + reset_parser(client); + +done: + if (error == GIT_RETRY) + error = 0; + + git_http_response_dispose(&response); + return error; +} + +bool git_http_client_has_response(git_http_client *client) +{ + return (client->state == HAS_EARLY_RESPONSE || + client->state > SENT_REQUEST); +} + +int git_http_client_send_body( + git_http_client *client, + const char *buffer, + size_t buffer_len) +{ + git_http_server *server; + git_buf hdr = GIT_BUF_INIT; + int error; + + assert(client); + + /* If we're waiting for proxy auth, don't sending more requests. */ + if (client->state == HAS_EARLY_RESPONSE) + return 0; + + if (client->state != SENDING_BODY) { + git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); + return -1; + } + + if (!buffer_len) + return 0; + + server = &client->server; + + if (client->request_body_len) { + assert(buffer_len <= client->request_body_remain); + + if ((error = stream_write(server, buffer, buffer_len)) < 0) + goto done; + + client->request_body_remain -= buffer_len; + } else { + if ((error = git_buf_printf(&hdr, "%" PRIxZ "\r\n", buffer_len)) < 0 || + (error = stream_write(server, hdr.ptr, hdr.size)) < 0 || + (error = stream_write(server, buffer, buffer_len)) < 0 || + (error = stream_write(server, "\r\n", 2)) < 0) + goto done; + } + +done: + git_buf_dispose(&hdr); + return error; +} + +static int complete_request(git_http_client *client) +{ + int error = 0; + + assert(client && client->state == SENDING_BODY); + + if (client->request_body_len && client->request_body_remain) { + git_error_set(GIT_ERROR_HTTP, "truncated write"); + error = -1; + } else if (client->request_chunked) { + error = stream_write(&client->server, "0\r\n\r\n", 5); + } + + client->state = SENT_REQUEST; + return error; +} + +int git_http_client_read_response( + git_http_response *response, + git_http_client *client) +{ + http_parser_context parser_context = {0}; + int error; + + assert(response && client); + + if (client->state == SENDING_BODY) { + if ((error = complete_request(client)) < 0) + goto done; + } + + if (client->state == HAS_EARLY_RESPONSE) { + memcpy(response, &client->early_response, sizeof(git_http_response)); + memset(&client->early_response, 0, sizeof(git_http_response)); + client->state = DONE; + return 0; + } + + if (client->state != SENT_REQUEST) { + git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); + error = -1; + goto done; + } + + git_http_response_dispose(response); + + git_vector_free_deep(&client->server.auth_challenges); + git_vector_free_deep(&client->proxy.auth_challenges); + + client->state = READING_RESPONSE; + client->keepalive = 0; + client->parser.data = &parser_context; + + parser_context.client = client; + parser_context.response = response; + + while (client->state == READING_RESPONSE) { + if ((error = client_read_and_parse(client)) < 0) + goto done; + } + + assert(client->state == READING_BODY || client->state == DONE); + +done: + git_buf_dispose(&parser_context.parse_header_name); + git_buf_dispose(&parser_context.parse_header_value); + + return error; +} + +int git_http_client_read_body( + git_http_client *client, + char *buffer, + size_t buffer_size) +{ + http_parser_context parser_context = {0}; + int error = 0; + + if (client->state == DONE) + return 0; + + if (client->state != READING_BODY) { + git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); + return -1; + } + + /* + * Now we'll read from the socket and http_parser will pipeline the + * data directly to the client. + */ + + parser_context.client = client; + parser_context.output_buf = buffer; + parser_context.output_size = buffer_size; + + client->parser.data = &parser_context; + + /* + * Clients expect to get a non-zero amount of data from us, + * so we either block until we have data to return, until we + * hit EOF or there's an error. Do this in a loop, since we + * may end up reading only some stream metadata (like chunk + * information). + */ + while (!parser_context.output_written) { + error = client_read_and_parse(client); + + if (error <= 0) + goto done; + + if (client->state == DONE) + break; + } + + assert(parser_context.output_written <= INT_MAX); + error = (int)parser_context.output_written; + +done: + if (error < 0) + client->connected = 0; + + return error; +} + +int git_http_client_skip_body(git_http_client *client) +{ + http_parser_context parser_context = {0}; + int error; + + if (client->state == DONE) + return 0; + + if (client->state != READING_BODY) { + git_error_set(GIT_ERROR_HTTP, "client is in invalid state"); + return -1; + } + + parser_context.client = client; + client->parser.data = &parser_context; + + do { + error = client_read_and_parse(client); + + if (parser_context.error != HPE_OK || + (parser_context.parse_status != PARSE_STATUS_OK && + parser_context.parse_status != PARSE_STATUS_NO_OUTPUT)) { + git_error_set(GIT_ERROR_HTTP, + "unexpected data handled in callback"); + error = -1; + } + } while (!error); + + if (error < 0) + client->connected = 0; + + return error; +} + +/* + * Create an http_client capable of communicating with the given remote + * host. + */ +int git_http_client_new( + git_http_client **out, + git_http_client_options *opts) +{ + git_http_client *client; + + assert(out); + + client = git__calloc(1, sizeof(git_http_client)); + GIT_ERROR_CHECK_ALLOC(client); + + git_buf_init(&client->read_buf, GIT_READ_BUFFER_SIZE); + GIT_ERROR_CHECK_ALLOC(client->read_buf.ptr); + + if (opts) + memcpy(&client->opts, opts, sizeof(git_http_client_options)); + + *out = client; + return 0; +} + +GIT_INLINE(void) http_server_close(git_http_server *server) +{ + if (server->stream) { + git_stream_close(server->stream); + git_stream_free(server->stream); + server->stream = NULL; + } + + git_net_url_dispose(&server->url); + + git_vector_free_deep(&server->auth_challenges); + free_auth_context(server); +} + +static void http_client_close(git_http_client *client) +{ + http_server_close(&client->server); + http_server_close(&client->proxy); + + git_buf_dispose(&client->request_msg); + + client->state = 0; + client->request_count = 0; + client->connected = 0; + client->keepalive = 0; +} + +void git_http_client_free(git_http_client *client) +{ + if (!client) + return; + + http_client_close(client); + git_buf_dispose(&client->read_buf); + git__free(client); +} diff --git a/src/transports/httpclient.h b/src/transports/httpclient.h new file mode 100644 index 000000000..2a83bee3e --- /dev/null +++ b/src/transports/httpclient.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_transports_httpclient_h__ +#define INCLUDE_transports_httpclient_h__ + +#include "common.h" +#include "net.h" + +#define GIT_HTTP_STATUS_CONTINUE 100 +#define GIT_HTTP_STATUS_OK 200 +#define GIT_HTTP_MOVED_PERMANENTLY 301 +#define GIT_HTTP_FOUND 302 +#define GIT_HTTP_SEE_OTHER 303 +#define GIT_HTTP_TEMPORARY_REDIRECT 307 +#define GIT_HTTP_PERMANENT_REDIRECT 308 +#define GIT_HTTP_STATUS_UNAUTHORIZED 401 +#define GIT_HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED 407 + +typedef struct git_http_client git_http_client; + +/** Method for the HTTP request */ +typedef enum { + GIT_HTTP_METHOD_GET, + GIT_HTTP_METHOD_POST, + GIT_HTTP_METHOD_CONNECT +} git_http_method; + +/** An HTTP request */ +typedef struct { + git_http_method method; /**< Method for the request */ + git_net_url *url; /**< Full request URL */ + git_net_url *proxy; /**< Proxy to use */ + + /* Headers */ + const char *accept; /**< Contents of the Accept header */ + const char *content_type; /**< Content-Type header (for POST) */ + git_credential *credentials; /**< Credentials to authenticate with */ + git_credential *proxy_credentials; /**< Credentials for proxy */ + git_strarray *custom_headers; /**< Additional headers to deliver */ + + /* To POST a payload, either set content_length OR set chunked. */ + size_t content_length; /**< Length of the POST body */ + unsigned chunked : 1, /**< Post with chunking */ + expect_continue : 1; /**< Use expect/continue negotiation */ +} git_http_request; + +typedef struct { + int status; + + /* Headers */ + char *content_type; + size_t content_length; + char *location; + + /* Authentication headers */ + unsigned server_auth_schemetypes; /**< Schemes requested by remote */ + unsigned server_auth_credtypes; /**< Supported cred types for remote */ + + unsigned proxy_auth_schemetypes; /**< Schemes requested by proxy */ + unsigned proxy_auth_credtypes; /**< Supported cred types for proxy */ + + unsigned chunked : 1, /**< Response body is chunked */ + resend_credentials : 1; /**< Resend with authentication */ +} git_http_response; + +typedef struct { + /** Certificate check callback for the remote */ + git_transport_certificate_check_cb server_certificate_check_cb; + void *server_certificate_check_payload; + + /** Certificate check callback for the proxy */ + git_transport_certificate_check_cb proxy_certificate_check_cb; + void *proxy_certificate_check_payload; +} git_http_client_options; + +/** + * Create a new httpclient instance with the given options. + * + * @param out pointer to receive the new instance + * @param opts options to create the client with or NULL for defaults + */ +extern int git_http_client_new( + git_http_client **out, + git_http_client_options *opts); + +/* + * Sends a request to the host specified by the request URL. If the + * method is POST, either the the content_length or the chunked flag must + * be specified. The body should be provided in subsequent calls to + * git_http_client_send_body. + * + * @param client the client to write the request to + * @param request the request to send + */ +extern int git_http_client_send_request( + git_http_client *client, + git_http_request *request); + +/* + * After sending a request, there may already be a response to read -- + * either because there was a non-continue response to an expect: continue + * request, or because the server pipelined a response to us before we even + * sent the request. Examine the state. + * + * @param client the client to examine + * @return true if there's already a response to read, false otherwise + */ +extern bool git_http_client_has_response(git_http_client *client); + +/** + * Sends the given buffer to the remote as part of the request body. The + * request must have specified either a content_length or the chunked flag. + * + * @param client the client to write the request body to + * @param buffer the request body + * @param buffer_len number of bytes of the buffer to send + */ +extern int git_http_client_send_body( + git_http_client *client, + const char *buffer, + size_t buffer_len); + +/** + * Reads the headers of a response to a request. This will consume the + * entirety of the headers of a response from the server. The body (if any) + * can be read by calling git_http_client_read_body. Callers must free + * the response with git_http_response_dispose. + * + * @param response pointer to the response object to fill + * @param client the client to read the response from + */ +extern int git_http_client_read_response( + git_http_response *response, + git_http_client *client); + +/** + * Reads some or all of the body of a response. At most buffer_size (or + * INT_MAX) bytes will be read and placed into the buffer provided. The + * number of bytes read will be returned, or 0 to indicate that the end of + * the body has been read. + * + * @param client the client to read the response from + * @param buffer pointer to the buffer to fill + * @param buffer_size the maximum number of bytes to read + * @return the number of bytes read, 0 on end of body, or error code + */ +extern int git_http_client_read_body( + git_http_client *client, + char *buffer, + size_t buffer_size); + +/** + * Reads all of the (remainder of the) body of the response and ignores it. + * None of the data from the body will be returned to the caller. + * + * @param client the client to read the response from + * @return 0 or an error code + */ +extern int git_http_client_skip_body(git_http_client *client); + +/** + * Examines the status code of the response to determine if it is a + * redirect of any type (eg, 301, 302, etc). + * + * @param response the response to inspect + * @return true if the response is a redirect, false otherwise + */ +extern bool git_http_response_is_redirect(git_http_response *response); + +/** + * Frees any memory associated with the response. + * + * @param response the response to free + */ +extern void git_http_response_dispose(git_http_response *response); + +/** + * Frees any memory associated with the client. If any sockets are open, + * they will be closed. + * + * @param client the client to free + */ +extern void git_http_client_free(git_http_client *client); + +#endif diff --git a/src/transports/local.c b/src/transports/local.c index b544491ef..24af7f063 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -201,7 +201,7 @@ on_error: static int local_connect( git_transport *transport, const char *url, - git_cred_acquire_cb cred_acquire_cb, + git_credential_acquire_cb cred_acquire_cb, void *cred_acquire_payload, const git_proxy_options *proxy, int direction, int flags) @@ -309,7 +309,7 @@ static int local_push_update_remote_ref( if (lref[0] != '\0') { /* Create or update a ref */ error = git_reference_create(NULL, remote_repo, rref, loid, - !git_oid_iszero(roid), NULL); + !git_oid_is_zero(roid), NULL); } else { /* Delete a ref */ if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) { @@ -325,7 +325,7 @@ static int local_push_update_remote_ref( return error; } -static int transfer_to_push_transfer(const git_transfer_progress *stats, void *payload) +static int transfer_to_push_transfer(const git_indexer_progress *stats, void *payload) { const git_remote_callbacks *cbs = payload; @@ -460,8 +460,8 @@ on_error: } typedef struct foreach_data { - git_transfer_progress *stats; - git_transfer_progress_cb progress_cb; + git_indexer_progress *stats; + git_indexer_progress_cb progress_cb; void *progress_payload; git_odb_writepack *writepack; } foreach_data; @@ -501,7 +501,7 @@ static int local_counting(int stage, unsigned int current, unsigned int total, v if (git_buf_oom(&progress_info)) return -1; - error = t->progress_cb(git_buf_cstr(&progress_info), git_buf_len(&progress_info), t->message_cb_payload); + error = t->progress_cb(git_buf_cstr(&progress_info), (int)git_buf_len(&progress_info), t->message_cb_payload); git_buf_dispose(&progress_info); return error; @@ -533,8 +533,8 @@ static int foreach_reference_cb(git_reference *reference, void *payload) static int local_download_pack( git_transport *transport, git_repository *repo, - git_transfer_progress *stats, - git_transfer_progress_cb progress_cb, + git_indexer_progress *stats, + git_indexer_progress_cb progress_cb, void *progress_payload) { transport_local *t = (transport_local*)transport; @@ -588,7 +588,7 @@ static int local_download_pack( goto cleanup; if (t->progress_cb && - (error = t->progress_cb(git_buf_cstr(&progress_info), git_buf_len(&progress_info), t->message_cb_payload)) < 0) + (error = t->progress_cb(git_buf_cstr(&progress_info), (int)git_buf_len(&progress_info), t->message_cb_payload)) < 0) goto cleanup; /* Walk the objects, building a packfile */ @@ -602,7 +602,7 @@ static int local_download_pack( goto cleanup; if (t->progress_cb && - (error = t->progress_cb(git_buf_cstr(&progress_info), git_buf_len(&progress_info), t->message_cb_payload)) < 0) + (error = t->progress_cb(git_buf_cstr(&progress_info), (int)git_buf_len(&progress_info), t->message_cb_payload)) < 0) goto cleanup; if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0) diff --git a/src/transports/smart.c b/src/transports/smart.c index fd7113c7e..bb4d2a228 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -63,7 +63,7 @@ static int git_smart__set_callbacks( git_transport_certificate_check_cb certificate_check_cb, void *message_cb_payload) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); t->progress_cb = progress_cb; t->error_cb = error_cb; @@ -73,7 +73,7 @@ static int git_smart__set_callbacks( return 0; } -static int http_header_name_length(const char *http_header) +static size_t http_header_name_length(const char *http_header) { const char *colon = strchr(http_header, ':'); if (!colon) @@ -84,7 +84,7 @@ static int http_header_name_length(const char *http_header) static bool is_malformed_http_header(const char *http_header) { const char *c; - int name_len; + size_t name_len; /* Disallow \r and \n */ c = strchr(http_header, '\r'); @@ -114,7 +114,7 @@ static char *forbidden_custom_headers[] = { static bool is_forbidden_custom_header(const char *custom_header) { unsigned long i; - int name_len = http_header_name_length(custom_header); + size_t name_len = http_header_name_length(custom_header); /* Disallow headers that we set */ for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++) @@ -128,7 +128,7 @@ static int git_smart__set_custom_headers( git_transport *transport, const git_strarray *custom_headers) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); size_t i; if (t->custom_headers.count) @@ -206,13 +206,13 @@ static void free_symrefs(git_vector *symrefs) static int git_smart__connect( git_transport *transport, const char *url, - git_cred_acquire_cb cred_acquire_cb, + git_credential_acquire_cb cred_acquire_cb, void *cred_acquire_payload, const git_proxy_options *proxy, int direction, int flags) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); git_smart_subtransport_stream *stream; int error; git_pkt *pkt; @@ -286,7 +286,7 @@ static int git_smart__connect( if ((error = git_smart__detect_caps(first, &t->caps, &symrefs)) == 0) { /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */ if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") && - git_oid_iszero(&first->head.oid)) { + git_oid_is_zero(&first->head.oid)) { git_vector_clear(&t->refs); git_pkt_free((git_pkt *)first); } @@ -315,7 +315,7 @@ cleanup: static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); if (!t->have_refs) { git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs"); @@ -330,7 +330,7 @@ static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transpo int git_smart__negotiation_step(git_transport *transport, void *data, size_t len) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); git_smart_subtransport_stream *stream; int error; @@ -387,21 +387,21 @@ int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream static void git_smart__cancel(git_transport *transport) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); git_atomic_set(&t->cancelled, 1); } static int git_smart__is_connected(git_transport *transport) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); return t->connected; } static int git_smart__read_flags(git_transport *transport, int *flags) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); *flags = t->flags; @@ -410,7 +410,7 @@ static int git_smart__read_flags(git_transport *transport, int *flags) static int git_smart__close(git_transport *transport) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); git_vector *common = &t->common; unsigned int i; git_pkt *p; @@ -447,7 +447,7 @@ static int git_smart__close(git_transport *transport) static void git_smart__free(git_transport *transport) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); git_vector *refs = &t->refs; unsigned int i; git_pkt *p; @@ -479,7 +479,7 @@ static int ref_name_cmp(const void *a, const void *b) int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); assert(transport && cert && hostname); @@ -489,9 +489,9 @@ int git_transport_smart_certificate_check(git_transport *transport, git_cert *ce return t->certificate_check_cb(cert, valid, hostname, t->message_cb_payload); } -int git_transport_smart_credentials(git_cred **out, git_transport *transport, const char *user, int methods) +int git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods) { - transport_smart *t = (transport_smart *)transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); assert(out && transport); @@ -503,7 +503,7 @@ int git_transport_smart_credentials(git_cred **out, git_transport *transport, co int git_transport_smart_proxy_options(git_proxy_options *out, git_transport *transport) { - transport_smart *t = (transport_smart *) transport; + transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent); return git_proxy_options_dup(out, &t->proxy); } diff --git a/src/transports/smart.h b/src/transports/smart.h index abf80286b..18e0b7e9f 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -137,7 +137,7 @@ typedef struct { git_transport parent; git_remote *owner; char *url; - git_cred_acquire_cb cred_acquire_cb; + git_credential_acquire_cb cred_acquire_cb; void *cred_acquire_payload; git_proxy_options proxy; int direction; @@ -177,8 +177,8 @@ int git_smart__negotiate_fetch( int git_smart__download_pack( git_transport *transport, git_repository *repo, - git_transfer_progress *stats, - git_transfer_progress_cb progress_cb, + git_indexer_progress *stats, + git_indexer_progress_cb progress_cb, void *progress_payload); /* smart.c */ diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index 9bc273e0c..56b680d28 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -273,7 +273,7 @@ static int ok_pkt(git_pkt **out, const char *line, size_t len) line += 3; len -= 3; - if (line[len - 1] == '\n') + if (len && line[len - 1] == '\n') --len; GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1); diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 2d8a1d83f..c01656dc4 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -17,6 +17,7 @@ #include "pack-objects.h" #include "remote.h" #include "util.h" +#include "revwalk.h" #define NETWORK_XFER_THRESHOLD (100*1024) /* The minimal interval between progress updates (in seconds). */ @@ -270,50 +271,6 @@ static int store_common(transport_smart *t) return 0; } -static int fetch_setup_walk(git_revwalk **out, git_repository *repo) -{ - git_revwalk *walk = NULL; - git_strarray refs; - unsigned int i; - git_reference *ref = NULL; - int error; - - if ((error = git_reference_list(&refs, repo)) < 0) - return error; - - if ((error = git_revwalk_new(&walk, repo)) < 0) - return error; - - git_revwalk_sorting(walk, GIT_SORT_TIME); - - for (i = 0; i < refs.count; ++i) { - git_reference_free(ref); - ref = NULL; - - /* No tags */ - if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR)) - continue; - - if ((error = git_reference_lookup(&ref, repo, refs.strings[i])) < 0) - goto on_error; - - if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) - continue; - - if ((error = git_revwalk_push(walk, git_reference_target(ref))) < 0) - goto on_error; - } - - *out = walk; - -on_error: - if (error) - git_revwalk_free(walk); - git_reference_free(ref); - git_strarray_free(&refs); - return error; -} - static int wait_while_ack(gitno_buffer *buf) { int error; @@ -347,6 +304,7 @@ static int wait_while_ack(gitno_buffer *buf) int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *wants, size_t count) { transport_smart *t = (transport_smart *)transport; + git_revwalk__push_options opts = GIT_REVWALK__PUSH_OPTIONS_INIT; gitno_buffer *buf = &t->buffer; git_buf data = GIT_BUF_INIT; git_revwalk *walk = NULL; @@ -358,7 +316,11 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) return error; - if ((error = fetch_setup_walk(&walk, repo)) < 0) + if ((error = git_revwalk_new(&walk, repo)) < 0) + goto on_error; + + opts.insert_by_date = 1; + if ((error = git_revwalk__push_glob(walk, "refs/*", &opts)) < 0) goto on_error; /* @@ -409,7 +371,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c } else if (pkt_type == GIT_PKT_NAK) { continue; } else { - git_error_set(GIT_ERROR_NET, "Unexpected pkt type"); + git_error_set(GIT_ERROR_NET, "unexpected pkt type"); error = -1; goto on_error; } @@ -477,7 +439,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c return error; if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) { - git_error_set(GIT_ERROR_NET, "Unexpected pkt type"); + git_error_set(GIT_ERROR_NET, "unexpected pkt type"); return -1; } } else { @@ -492,13 +454,13 @@ on_error: return error; } -static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, gitno_buffer *buf, git_transfer_progress *stats) +static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, gitno_buffer *buf, git_indexer_progress *stats) { int recvd; do { if (t->cancelled.val) { - git_error_set(GIT_ERROR_NET, "The fetch was cancelled by the user"); + git_error_set(GIT_ERROR_NET, "the fetch was cancelled by the user"); return GIT_EUSER; } @@ -519,9 +481,9 @@ static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, struct network_packetsize_payload { - git_transfer_progress_cb callback; + git_indexer_progress_cb callback; void *payload; - git_transfer_progress *stats; + git_indexer_progress *stats; size_t last_fired_bytes; }; @@ -546,8 +508,8 @@ static int network_packetsize(size_t received, void *payload) int git_smart__download_pack( git_transport *transport, git_repository *repo, - git_transfer_progress *stats, - git_transfer_progress_cb transfer_progress_cb, + git_indexer_progress *stats, + git_indexer_progress_cb progress_cb, void *progress_payload) { transport_smart *t = (transport_smart *)transport; @@ -557,10 +519,10 @@ int git_smart__download_pack( int error = 0; struct network_packetsize_payload npp = {0}; - memset(stats, 0, sizeof(git_transfer_progress)); + memset(stats, 0, sizeof(git_indexer_progress)); - if (transfer_progress_cb) { - npp.callback = transfer_progress_cb; + if (progress_cb) { + npp.callback = progress_cb; npp.payload = progress_payload; npp.stats = stats; t->packetsize_cb = &network_packetsize; @@ -573,7 +535,7 @@ int git_smart__download_pack( } if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || - ((error = git_odb_write_pack(&writepack, odb, transfer_progress_cb, progress_payload)) != 0)) + ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0)) goto done; /* @@ -604,7 +566,14 @@ int git_smart__download_pack( } else if (pkt->type == GIT_PKT_PROGRESS) { if (t->progress_cb) { git_pkt_progress *p = (git_pkt_progress *) pkt; - error = t->progress_cb(p->data, p->len, t->message_cb_payload); + + if (p->len > INT_MAX) { + git_error_set(GIT_ERROR_NET, "oversized progress message"); + error = GIT_ERROR; + goto done; + } + + error = t->progress_cb(p->data, (int)p->len, t->message_cb_payload); } } else if (pkt->type == GIT_PKT_DATA) { git_pkt_data *p = (git_pkt_data *) pkt; @@ -626,7 +595,7 @@ int git_smart__download_pack( } while (1); /* - * Trailing execution of transfer_progress_cb, if necessary... + * Trailing execution of progress_cb, if necessary... * Only the callback through the npp datastructure currently * updates the last_fired_bytes value. It is possible that * progress has already been reported with the correct @@ -645,7 +614,7 @@ int git_smart__download_pack( done: if (writepack) writepack->free(writepack); - if (transfer_progress_cb) { + if (progress_cb) { t->packetsize_cb = NULL; t->packetsize_payload = NULL; } @@ -839,7 +808,14 @@ static int parse_report(transport_smart *transport, git_push *push) case GIT_PKT_PROGRESS: if (transport->progress_cb) { git_pkt_progress *p = (git_pkt_progress *) pkt; - error = transport->progress_cb(p->data, p->len, transport->message_cb_payload); + + if (p->len > INT_MAX) { + git_error_set(GIT_ERROR_NET, "oversized progress message"); + error = GIT_ERROR; + goto done; + } + + error = transport->progress_cb(p->data, (int)p->len, transport->message_cb_payload); } break; default: @@ -855,7 +831,7 @@ static int parse_report(transport_smart *transport, git_push *push) if (data_pkt_buf.size > 0) { /* If there was data remaining in the pack data buffer, * then the server sent a partial pkt-line */ - git_error_set(GIT_ERROR_NET, "Incomplete pack data pkt-line"); + git_error_set(GIT_ERROR_NET, "incomplete pack data pkt-line"); error = GIT_ERROR; } goto done; @@ -963,7 +939,7 @@ static int update_refs_from_report( /* Remove any refs which we updated to have a zero OID. */ git_vector_rforeach(refs, i, ref) { - if (git_oid_iszero(&ref->head.oid)) { + if (git_oid_is_zero(&ref->head.oid)) { git_vector_remove(refs, i); git_pkt_free((git_pkt *)ref); } @@ -978,7 +954,7 @@ struct push_packbuilder_payload { git_smart_subtransport_stream *stream; git_packbuilder *pb; - git_push_transfer_progress cb; + git_push_transfer_progress_cb cb; void *cb_payload; size_t last_bytes; double last_progress_report_time; diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 5a6058fb7..68b3cbeda 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -14,11 +14,14 @@ #include "global.h" #include "git2.h" #include "buffer.h" +#include "net.h" #include "netops.h" #include "smart.h" -#include "cred.h" #include "streams/socket.h" +#include "git2/credential.h" +#include "git2/sys/credential.h" + #ifdef GIT_SSH #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport) @@ -42,7 +45,7 @@ typedef struct { git_smart_subtransport parent; transport_smart *owner; ssh_stream *current_stream; - git_cred *cred; + git_credential *cred; char *cmd_uploadpack; char *cmd_receivepack; } ssh_subtransport; @@ -132,7 +135,7 @@ static int ssh_stream_read( size_t *bytes_read) { int rc; - ssh_stream *s = (ssh_stream *)stream; + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); *bytes_read = 0; @@ -170,7 +173,7 @@ static int ssh_stream_write( const char *buffer, size_t len) { - ssh_stream *s = (ssh_stream *)stream; + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); size_t off = 0; ssize_t ret = 0; @@ -196,7 +199,7 @@ static int ssh_stream_write( static void ssh_stream_free(git_smart_subtransport_stream *stream) { - ssh_stream *s = (ssh_stream *)stream; + ssh_stream *s = GIT_CONTAINER_OF(stream, ssh_stream, parent); ssh_subtransport *t; if (!stream) @@ -258,8 +261,7 @@ static int ssh_stream_alloc( } static int git_ssh_extract_url_parts( - char **host, - char **username, + git_net_url *urldata, const char *url) { char *colon, *at; @@ -271,11 +273,11 @@ static int git_ssh_extract_url_parts( at = strchr(url, '@'); if (at) { start = at + 1; - *username = git__substrdup(url, at - url); - GIT_ERROR_CHECK_ALLOC(*username); + urldata->username = git__substrdup(url, at - url); + GIT_ERROR_CHECK_ALLOC(urldata->username); } else { start = url; - *username = NULL; + urldata->username = NULL; } if (colon == NULL || (colon < start)) { @@ -283,13 +285,13 @@ static int git_ssh_extract_url_parts( return -1; } - *host = git__substrdup(start, colon - start); - GIT_ERROR_CHECK_ALLOC(*host); + urldata->host = git__substrdup(start, colon - start); + GIT_ERROR_CHECK_ALLOC(urldata->host); return 0; } -static int ssh_agent_auth(LIBSSH2_SESSION *session, git_cred_ssh_key *c) { +static int ssh_agent_auth(LIBSSH2_SESSION *session, git_credential_ssh_key *c) { int rc = LIBSSH2_ERROR_NONE; struct libssh2_agent_publickey *curr, *prev = NULL; @@ -344,21 +346,21 @@ shutdown: } static int _git_ssh_authenticate_session( - LIBSSH2_SESSION* session, - git_cred* cred) + LIBSSH2_SESSION *session, + git_credential *cred) { int rc; do { git_error_clear(); switch (cred->credtype) { - case GIT_CREDTYPE_USERPASS_PLAINTEXT: { - git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + case GIT_CREDENTIAL_USERPASS_PLAINTEXT: { + git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; rc = libssh2_userauth_password(session, c->username, c->password); break; } - case GIT_CREDTYPE_SSH_KEY: { - git_cred_ssh_key *c = (git_cred_ssh_key *)cred; + case GIT_CREDENTIAL_SSH_KEY: { + git_credential_ssh_key *c = (git_credential_ssh_key *)cred; if (c->privatekey) rc = libssh2_userauth_publickey_fromfile( @@ -369,17 +371,17 @@ static int _git_ssh_authenticate_session( break; } - case GIT_CREDTYPE_SSH_CUSTOM: { - git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred; + case GIT_CREDENTIAL_SSH_CUSTOM: { + git_credential_ssh_custom *c = (git_credential_ssh_custom *)cred; rc = libssh2_userauth_publickey( session, c->username, (const unsigned char *)c->publickey, c->publickey_len, c->sign_callback, &c->payload); break; } - case GIT_CREDTYPE_SSH_INTERACTIVE: { + case GIT_CREDENTIAL_SSH_INTERACTIVE: { void **abstract = libssh2_session_abstract(session); - git_cred_ssh_interactive *c = (git_cred_ssh_interactive *)cred; + git_credential_ssh_interactive *c = (git_credential_ssh_interactive *)cred; /* ideally, we should be able to set this by calling * libssh2_session_init_ex() instead of libssh2_session_init(). @@ -399,8 +401,8 @@ static int _git_ssh_authenticate_session( break; } #ifdef GIT_SSH_MEMORY_CREDENTIALS - case GIT_CREDTYPE_SSH_MEMORY: { - git_cred_ssh_key *c = (git_cred_ssh_key *)cred; + case GIT_CREDENTIAL_SSH_MEMORY: { + git_credential_ssh_key *c = (git_credential_ssh_key *)cred; assert(c->username); assert(c->privatekey); @@ -436,10 +438,10 @@ static int _git_ssh_authenticate_session( return 0; } -static int request_creds(git_cred **out, ssh_subtransport *t, const char *user, int auth_methods) +static int request_creds(git_credential **out, ssh_subtransport *t, const char *user, int auth_methods) { int error, no_callback = 0; - git_cred *cred = NULL; + git_credential *cred = NULL; if (!t->owner->cred_acquire_cb) { no_callback = 1; @@ -479,7 +481,7 @@ static int _git_ssh_session_create( { int rc = 0; LIBSSH2_SESSION* s; - git_socket_stream *socket = (git_socket_stream *) io; + git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent); assert(session); @@ -506,18 +508,19 @@ static int _git_ssh_session_create( return 0; } +#define SSH_DEFAULT_PORT "22" + static int _git_ssh_setup_conn( ssh_subtransport *t, const char *url, const char *cmd, git_smart_subtransport_stream **stream) { - char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; - const char *default_port="22"; + git_net_url urldata = GIT_NET_URL_INIT; int auth_methods, error = 0; size_t i; ssh_stream *s; - git_cred *cred = NULL; + git_credential *cred = NULL; LIBSSH2_SESSION* session=NULL; LIBSSH2_CHANNEL* channel=NULL; @@ -535,19 +538,22 @@ static int _git_ssh_setup_conn( const char *p = ssh_prefixes[i]; if (!git__prefixcmp(url, p)) { - if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0) + if ((error = git_net_url_parse(&urldata, url)) < 0) goto done; goto post_extract; } } - if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0) + if ((error = git_ssh_extract_url_parts(&urldata, url)) < 0) goto done; - port = git__strdup(default_port); - GIT_ERROR_CHECK_ALLOC(port); + + if (urldata.port == NULL) + urldata.port = git__strdup(SSH_DEFAULT_PORT); + + GIT_ERROR_CHECK_ALLOC(urldata.port); post_extract: - if ((error = git_socket_stream_new(&s->io, host, port)) < 0 || + if ((error = git_socket_stream_new(&s->io, urldata.host, urldata.port)) < 0 || (error = git_stream_connect(s->io)) < 0) goto done; @@ -560,6 +566,14 @@ post_extract: cert.parent.cert_type = GIT_CERT_HOSTKEY_LIBSSH2; +#ifdef LIBSSH2_HOSTKEY_HASH_SHA256 + key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256); + if (key != NULL) { + cert.type |= GIT_CERT_SSH_SHA256; + memcpy(&cert.hash_sha256, key, 32); + } +#endif + key = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); if (key != NULL) { cert.type |= GIT_CERT_SSH_SHA1; @@ -583,7 +597,7 @@ post_extract: cert_ptr = &cert; - error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, host, t->owner->message_cb_payload); + error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, urldata.host, t->owner->message_cb_payload); if (error < 0 && error != GIT_PASSTHROUGH) { if (!git_error_last()) @@ -594,21 +608,21 @@ post_extract: } /* we need the username to ask for auth methods */ - if (!user) { - if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0) + if (!urldata.username) { + if ((error = request_creds(&cred, t, NULL, GIT_CREDENTIAL_USERNAME)) < 0) goto done; - user = git__strdup(((git_cred_username *) cred)->username); + urldata.username = git__strdup(((git_credential_username *) cred)->username); cred->free(cred); cred = NULL; - if (!user) + if (!urldata.username) goto done; - } else if (user && pass) { - if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0) + } else if (urldata.username && urldata.password) { + if ((error = git_credential_userpass_plaintext_new(&cred, urldata.username, urldata.password)) < 0) goto done; } - if ((error = list_auth_methods(&auth_methods, session, user)) < 0) + if ((error = list_auth_methods(&auth_methods, session, urldata.username)) < 0) goto done; error = GIT_EAUTH; @@ -622,16 +636,24 @@ post_extract: cred = NULL; } - if ((error = request_creds(&cred, t, user, auth_methods)) < 0) + if ((error = request_creds(&cred, t, urldata.username, auth_methods)) < 0) goto done; - if (strcmp(user, git_cred__username(cred))) { + if (strcmp(urldata.username, git_credential_get_username(cred))) { git_error_set(GIT_ERROR_SSH, "username does not match previous request"); error = -1; goto done; } error = _git_ssh_authenticate_session(session, cred); + + if (error == GIT_EAUTH) { + /* refresh auth methods */ + if ((error = list_auth_methods(&auth_methods, session, urldata.username)) < 0) + goto done; + else + error = GIT_EAUTH; + } } if (error < 0) @@ -662,11 +684,7 @@ done: if (cred) cred->free(cred); - git__free(host); - git__free(port); - git__free(path); - git__free(user); - git__free(pass); + git_net_url_dispose(&urldata); return error; } @@ -730,7 +748,7 @@ static int _ssh_action( const char *url, git_smart_service_t action) { - ssh_subtransport *t = (ssh_subtransport *) subtransport; + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); switch (action) { case GIT_SERVICE_UPLOADPACK_LS: @@ -752,7 +770,7 @@ static int _ssh_action( static int _ssh_close(git_smart_subtransport *subtransport) { - ssh_subtransport *t = (ssh_subtransport *) subtransport; + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); assert(!t->current_stream); @@ -763,7 +781,7 @@ static int _ssh_close(git_smart_subtransport *subtransport) static void _ssh_free(git_smart_subtransport *subtransport) { - ssh_subtransport *t = (ssh_subtransport *) subtransport; + ssh_subtransport *t = GIT_CONTAINER_OF(subtransport, ssh_subtransport, parent); assert(!t->current_stream); @@ -796,23 +814,23 @@ static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *use ptr++; if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { - *out |= GIT_CREDTYPE_SSH_KEY; - *out |= GIT_CREDTYPE_SSH_CUSTOM; + *out |= GIT_CREDENTIAL_SSH_KEY; + *out |= GIT_CREDENTIAL_SSH_CUSTOM; #ifdef GIT_SSH_MEMORY_CREDENTIALS - *out |= GIT_CREDTYPE_SSH_MEMORY; + *out |= GIT_CREDENTIAL_SSH_MEMORY; #endif ptr += strlen(SSH_AUTH_PUBLICKEY); continue; } if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) { - *out |= GIT_CREDTYPE_USERPASS_PLAINTEXT; + *out |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; ptr += strlen(SSH_AUTH_PASSWORD); continue; } if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) { - *out |= GIT_CREDTYPE_SSH_INTERACTIVE; + *out |= GIT_CREDENTIAL_SSH_INTERACTIVE; ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE); continue; } diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index f922cb480..ee8cb1543 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -19,6 +19,7 @@ #include "repository.h" #include "global.h" #include "http.h" +#include "git2/sys/credential.h" #include #include @@ -48,6 +49,16 @@ # define WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 0x00000800 #endif +#ifndef HTTP_STATUS_PERMANENT_REDIRECT +# define HTTP_STATUS_PERMANENT_REDIRECT 308 +#endif + +#ifndef DWORD_MAX +# define DWORD_MAX 0xffffffff +#endif + +bool git_http__expect_continue = false; + static const char *prefix_https = "https://"; static const char *upload_pack_service = "upload-pack"; static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; @@ -100,24 +111,44 @@ typedef struct { chunked : 1; } winhttp_stream; +typedef struct { + git_net_url url; + git_credential *cred; + int auth_mechanisms; + bool url_cred_presented; +} winhttp_server; + typedef struct { git_smart_subtransport parent; transport_smart *owner; - gitno_connection_data connection_data; - gitno_connection_data proxy_connection_data; - git_cred *cred; - git_cred *url_cred; - git_cred *proxy_cred; - int auth_mechanisms; + + winhttp_server server; + winhttp_server proxy; + HINTERNET session; HINTERNET connection; } winhttp_subtransport; -static int _apply_userpass_credential(HINTERNET request, DWORD target, DWORD scheme, git_cred *cred) +static int apply_userpass_credentials(HINTERNET request, DWORD target, int mechanisms, git_credential *cred) { - git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; - wchar_t *user, *pass; + git_credential_userpass_plaintext *c = (git_credential_userpass_plaintext *)cred; + wchar_t *user = NULL, *pass = NULL; int user_len = 0, pass_len = 0, error = 0; + DWORD native_scheme; + + if (mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) { + native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE; + } else if (mechanisms & GIT_WINHTTP_AUTH_NTLM) { + native_scheme = WINHTTP_AUTH_SCHEME_NTLM; + } else if (mechanisms & GIT_WINHTTP_AUTH_DIGEST) { + native_scheme = WINHTTP_AUTH_SCHEME_DIGEST; + } else if (mechanisms & GIT_WINHTTP_AUTH_BASIC) { + native_scheme = WINHTTP_AUTH_SCHEME_BASIC; + } else { + git_error_set(GIT_ERROR_HTTP, "invalid authentication scheme"); + error = -1; + goto done; + } if ((error = user_len = git__utf8_to_16_alloc(&user, c->username)) < 0) goto done; @@ -125,7 +156,7 @@ static int _apply_userpass_credential(HINTERNET request, DWORD target, DWORD sch if ((error = pass_len = git__utf8_to_16_alloc(&pass, c->password)) < 0) goto done; - if (!WinHttpSetCredentials(request, target, scheme, user, pass, NULL)) { + if (!WinHttpSetCredentials(request, target, native_scheme, user, pass, NULL)) { git_error_set(GIT_ERROR_OS, "failed to set credentials"); error = -1; } @@ -143,81 +174,62 @@ done: return error; } -static int apply_userpass_credential_proxy(HINTERNET request, git_cred *cred, int mechanisms) +static int apply_default_credentials(HINTERNET request, DWORD target, int mechanisms) { - if (GIT_WINHTTP_AUTH_DIGEST & mechanisms) { - return _apply_userpass_credential(request, WINHTTP_AUTH_TARGET_PROXY, - WINHTTP_AUTH_SCHEME_DIGEST, cred); - } - - return _apply_userpass_credential(request, WINHTTP_AUTH_TARGET_PROXY, - WINHTTP_AUTH_SCHEME_BASIC, cred); -} - -static int apply_userpass_credential(HINTERNET request, int mechanisms, git_cred *cred) -{ - DWORD native_scheme; - - if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) || - (mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE)) { - native_scheme = WINHTTP_AUTH_SCHEME_NTLM; - } else if (mechanisms & GIT_WINHTTP_AUTH_BASIC) { - native_scheme = WINHTTP_AUTH_SCHEME_BASIC; - } else { - git_error_set(GIT_ERROR_NET, "invalid authentication scheme"); - return -1; - } - - return _apply_userpass_credential(request, WINHTTP_AUTH_TARGET_SERVER, - native_scheme, cred); -} - -static int apply_default_credentials(HINTERNET request, int mechanisms) -{ - /* Either the caller explicitly requested that default credentials be passed, - * or our fallback credential callback was invoked and checked that the target - * URI was in the appropriate Internet Explorer security zone. By setting this - * flag, we guarantee that the credentials are delivered by WinHTTP. The default - * is "medium" which applies to the intranet and sounds like it would correspond - * to Internet Explorer security zones, but in fact does not. */ - DWORD data = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW; + DWORD autologon_level = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW; DWORD native_scheme = 0; - if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) != 0) - native_scheme = WINHTTP_AUTH_SCHEME_NTLM; - - if ((mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) != 0) + if ((mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) != 0) { native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE; - - if (!native_scheme) { - git_error_set(GIT_ERROR_NET, "invalid authentication scheme"); + } else if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) != 0) { + native_scheme = WINHTTP_AUTH_SCHEME_NTLM; + } else { + git_error_set(GIT_ERROR_HTTP, "invalid authentication scheme"); return -1; } - if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &data, sizeof(DWORD))) + /* + * Autologon policy must be "low" to use default creds. + * This is safe as the user has explicitly requested it. + */ + if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &autologon_level, sizeof(DWORD))) { + git_error_set(GIT_ERROR_OS, "could not configure logon policy"); return -1; + } - if (!WinHttpSetCredentials(request, WINHTTP_AUTH_TARGET_SERVER, native_scheme, NULL, NULL, NULL)) + if (!WinHttpSetCredentials(request, target, native_scheme, NULL, NULL, NULL)) { + git_error_set(GIT_ERROR_OS, "could not configure credentials"); return -1; + } return 0; } -static int fallback_cred_acquire_cb( - git_cred **cred, - const char *url, - const char *username_from_url, +static int acquire_url_cred( + git_credential **cred, unsigned int allowed_types, - void *payload) + const char *username, + const char *password) +{ + if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) + return git_credential_userpass_plaintext_new(cred, username, password); + + if ((allowed_types & GIT_CREDENTIAL_DEFAULT) && *username == '\0' && *password == '\0') + return git_credential_default_new(cred); + + return 1; +} + +static int acquire_fallback_cred( + git_credential **cred, + const char *url, + unsigned int allowed_types) { int error = 1; - GIT_UNUSED(username_from_url); - GIT_UNUSED(payload); - /* If the target URI supports integrated Windows authentication * as an authentication mechanism */ - if (GIT_CREDTYPE_DEFAULT & allowed_types) { + if (GIT_CREDENTIAL_DEFAULT & allowed_types) { wchar_t *wide_url; HRESULT hCoInitResult; @@ -241,21 +253,21 @@ static int fallback_cred_acquire_cb( (URLZONE_LOCAL_MACHINE == dwZone || URLZONE_INTRANET == dwZone || URLZONE_TRUSTED == dwZone)) { - git_cred *existing = *cred; + git_credential *existing = *cred; if (existing) existing->free(existing); /* Then use default Windows credentials to authenticate this request */ - error = git_cred_default_new(cred); + error = git_credential_default_new(cred); } pISM->lpVtbl->Release(pISM); } - if (SUCCEEDED(hCoInitResult)) - /* Only unitialize if the call to CoInitializeEx was successful. */ - CoUninitialize(); + /* Only unitialize if the call to CoInitializeEx was successful. */ + if (SUCCEEDED(hCoInitResult)) + CoUninitialize(); } git__free(wide_url); @@ -275,12 +287,12 @@ static int certificate_check(winhttp_stream *s, int valid) /* If there is no override, we should fail if WinHTTP doesn't think it's fine */ if (t->owner->certificate_check_cb == NULL && !valid) { if (!git_error_last()) - git_error_set(GIT_ERROR_NET, "unknown certificate check failure"); + git_error_set(GIT_ERROR_HTTP, "unknown certificate check failure"); return GIT_ECERTIFICATE; } - if (t->owner->certificate_check_cb == NULL || !t->connection_data.use_ssl) + if (t->owner->certificate_check_cb == NULL || git__strcmp(t->server.url.scheme, "https") != 0) return 0; if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) { @@ -292,14 +304,14 @@ static int certificate_check(winhttp_stream *s, int valid) cert.parent.cert_type = GIT_CERT_X509; cert.data = cert_ctx->pbCertEncoded; cert.len = cert_ctx->cbCertEncoded; - error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->connection_data.host, t->owner->message_cb_payload); + error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->server.url.host, t->owner->message_cb_payload); CertFreeCertificateContext(cert_ctx); if (error == GIT_PASSTHROUGH) error = valid ? 0 : GIT_ECERTIFICATE; if (error < 0 && !git_error_last()) - git_error_set(GIT_ERROR_NET, "user cancelled certificate check"); + git_error_set(GIT_ERROR_HTTP, "user cancelled certificate check"); return error; } @@ -329,8 +341,25 @@ static void winhttp_stream_close(winhttp_stream *s) s->sent_request = 0; } -#define SCHEME_HTTP "http://" -#define SCHEME_HTTPS "https://" +static int apply_credentials( + HINTERNET request, + git_net_url *url, + int target, + git_credential *creds, + int mechanisms) +{ + int error = 0; + + GIT_UNUSED(url); + + /* If we have creds, just apply them */ + if (creds && creds->credtype == GIT_CREDENTIAL_USERPASS_PLAINTEXT) + error = apply_userpass_credentials(request, target, mechanisms, creds); + else if (creds && creds->credtype == GIT_CREDENTIAL_DEFAULT) + error = apply_default_credentials(request, target, mechanisms); + + return error; +} static int winhttp_stream_connect(winhttp_stream *s) { @@ -344,11 +373,17 @@ static int winhttp_stream_connect(winhttp_stream *s) unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS; int default_timeout = TIMEOUT_INFINITE; int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; + DWORD autologon_policy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH; + + const char *service_url = s->service_url; size_t i; const git_proxy_options *proxy_opts; + /* If path already ends in /, remove the leading slash from service_url */ + if ((git__suffixcmp(t->server.url.path, "/") == 0) && (git__prefixcmp(service_url, "/") == 0)) + service_url++; /* Prepare URL */ - git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url); + git_buf_printf(&buf, "%s%s", t->server.url.path, service_url); if (git_buf_oom(&buf)) return -1; @@ -367,13 +402,17 @@ static int winhttp_stream_connect(winhttp_stream *s) NULL, WINHTTP_NO_REFERER, types, - t->connection_data.use_ssl ? WINHTTP_FLAG_SECURE : 0); + git__strcmp(t->server.url.scheme, "https") == 0 ? WINHTTP_FLAG_SECURE : 0); if (!s->request) { git_error_set(GIT_ERROR_OS, "failed to open request"); goto on_error; } + /* Never attempt default credentials; we'll provide them explicitly. */ + if (!WinHttpSetOption(s->request, WINHTTP_OPTION_AUTOLOGON_POLICY, &autologon_policy, sizeof(DWORD))) + return -1; + if (!WinHttpSetTimeouts(s->request, default_timeout, default_connect_timeout, default_timeout, default_timeout)) { git_error_set(GIT_ERROR_OS, "failed to set timeouts for WinHTTP"); goto on_error; @@ -382,7 +421,7 @@ static int winhttp_stream_connect(winhttp_stream *s) proxy_opts = &t->owner->proxy; if (proxy_opts->type == GIT_PROXY_AUTO) { /* Set proxy if necessary */ - if (git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url) < 0) + if (git_remote__get_http_proxy(t->owner->owner, (strcmp(t->server.url.scheme, "https") == 0), &proxy_url) < 0) goto on_error; } else if (proxy_opts->type == GIT_PROXY_SPECIFIED) { @@ -395,38 +434,24 @@ static int winhttp_stream_connect(winhttp_stream *s) WINHTTP_PROXY_INFO proxy_info; wchar_t *proxy_wide; - if (!git__prefixcmp(proxy_url, SCHEME_HTTP)) { - t->proxy_connection_data.use_ssl = false; - } else if (!git__prefixcmp(proxy_url, SCHEME_HTTPS)) { - t->proxy_connection_data.use_ssl = true; - } else { - git_error_set(GIT_ERROR_NET, "invalid URL: '%s'", proxy_url); - return -1; - } + git_net_url_dispose(&t->proxy.url); - gitno_connection_data_free_ptrs(&t->proxy_connection_data); - - if ((error = gitno_extract_url_parts(&t->proxy_connection_data.host, &t->proxy_connection_data.port, NULL, - &t->proxy_connection_data.user, &t->proxy_connection_data.pass, proxy_url, NULL)) < 0) + if ((error = git_net_url_parse(&t->proxy.url, proxy_url)) < 0) goto on_error; - if (t->proxy_connection_data.user && t->proxy_connection_data.pass) { - if (t->proxy_cred) { - t->proxy_cred->free(t->proxy_cred); - } - - if ((error = git_cred_userpass_plaintext_new(&t->proxy_cred, t->proxy_connection_data.user, t->proxy_connection_data.pass)) < 0) - goto on_error; + if (strcmp(t->proxy.url.scheme, "http") != 0 && strcmp(t->proxy.url.scheme, "https") != 0) { + git_error_set(GIT_ERROR_HTTP, "invalid URL: '%s'", proxy_url); + error = -1; + goto on_error; } - if (t->proxy_connection_data.use_ssl) - git_buf_PUTS(&processed_url, SCHEME_HTTPS); - else - git_buf_PUTS(&processed_url, SCHEME_HTTP); + git_buf_puts(&processed_url, t->proxy.url.scheme); + git_buf_PUTS(&processed_url, "://"); - git_buf_puts(&processed_url, t->proxy_connection_data.host); - if (t->proxy_connection_data.port) - git_buf_printf(&processed_url, ":%s", t->proxy_connection_data.port); + git_buf_puts(&processed_url, t->proxy.url.host); + + if (!git_net_url_is_default_port(&t->proxy.url)) + git_buf_printf(&processed_url, ":%s", t->proxy.url.port); if (git_buf_oom(&processed_url)) { error = -1; @@ -454,13 +479,8 @@ static int winhttp_stream_connect(winhttp_stream *s) git__free(proxy_wide); - if (t->proxy_cred) { - if (t->proxy_cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT) { - if ((error = apply_userpass_credential_proxy(s->request, t->proxy_cred, t->auth_mechanisms)) < 0) - goto on_error; - } - } - + if ((error = apply_credentials(s->request, &t->proxy.url, WINHTTP_AUTH_TARGET_PROXY, t->proxy.cred, t->proxy.auth_mechanisms)) < 0) + goto on_error; } /* Disable WinHTTP redirects so we can handle them manually. Why, you ask? @@ -471,6 +491,7 @@ static int winhttp_stream_connect(winhttp_stream *s) &disable_redirects, sizeof(disable_redirects))) { git_error_set(GIT_ERROR_OS, "failed to disable redirects"); + error = -1; goto on_error; } @@ -543,32 +564,15 @@ static int winhttp_stream_connect(winhttp_stream *s) } /* If requested, disable certificate validation */ - if (t->connection_data.use_ssl) { + if (strcmp(t->server.url.scheme, "https") == 0) { int flags; if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0) goto on_error; } - /* If we have a credential on the subtransport, apply it to the request */ - if (t->cred && - t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT && - apply_userpass_credential(s->request, t->auth_mechanisms, t->cred) < 0) + if ((error = apply_credentials(s->request, &t->server.url, WINHTTP_AUTH_TARGET_SERVER, t->server.cred, t->server.auth_mechanisms)) < 0) goto on_error; - else if (t->cred && - t->cred->credtype == GIT_CREDTYPE_DEFAULT && - apply_default_credentials(s->request, t->auth_mechanisms) < 0) - goto on_error; - - /* If no other credentials have been applied and the URL has username and - * password, use those */ - if (!t->cred && t->connection_data.user && t->connection_data.pass) { - if (!t->url_cred && - git_cred_userpass_plaintext_new(&t->url_cred, t->connection_data.user, t->connection_data.pass) < 0) - goto on_error; - if (apply_userpass_credential(s->request, GIT_WINHTTP_AUTH_BASIC, t->url_cred) < 0) - goto on_error; - } /* We've done everything up to calling WinHttpSendRequest. */ @@ -584,9 +588,9 @@ on_error: } static int parse_unauthorized_response( - HINTERNET request, int *allowed_types, - int *allowed_mechanisms) + int *allowed_mechanisms, + HINTERNET request) { DWORD supported, first, target; @@ -602,23 +606,23 @@ static int parse_unauthorized_response( } if (WINHTTP_AUTH_SCHEME_NTLM & supported) { - *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; - *allowed_types |= GIT_CREDTYPE_DEFAULT; + *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; + *allowed_types |= GIT_CREDENTIAL_DEFAULT; *allowed_mechanisms |= GIT_WINHTTP_AUTH_NTLM; } if (WINHTTP_AUTH_SCHEME_NEGOTIATE & supported) { - *allowed_types |= GIT_CREDTYPE_DEFAULT; + *allowed_types |= GIT_CREDENTIAL_DEFAULT; *allowed_mechanisms |= GIT_WINHTTP_AUTH_NEGOTIATE; } if (WINHTTP_AUTH_SCHEME_BASIC & supported) { - *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; + *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; *allowed_mechanisms |= GIT_WINHTTP_AUTH_BASIC; } if (WINHTTP_AUTH_SCHEME_DIGEST & supported) { - *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; + *allowed_types |= GIT_CREDENTIAL_USERPASS_PLAINTEXT; *allowed_mechanisms |= GIT_WINHTTP_AUTH_DIGEST; } @@ -699,27 +703,31 @@ static void CALLBACK winhttp_status( { DWORD status; + GIT_UNUSED(connection); + GIT_UNUSED(ctx); + GIT_UNUSED(info_len); + if (code != WINHTTP_CALLBACK_STATUS_SECURE_FAILURE) return; status = *((DWORD *)info); if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)) - git_error_set(GIT_ERROR_NET, "SSL certificate issued for different common name"); + git_error_set(GIT_ERROR_HTTP, "SSL certificate issued for different common name"); else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID)) - git_error_set(GIT_ERROR_NET, "SSL certificate has expired"); + git_error_set(GIT_ERROR_HTTP, "SSL certificate has expired"); else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA)) - git_error_set(GIT_ERROR_NET, "SSL certificate signed by unknown CA"); + git_error_set(GIT_ERROR_HTTP, "SSL certificate signed by unknown CA"); else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT)) - git_error_set(GIT_ERROR_NET, "SSL certificate is invalid"); + git_error_set(GIT_ERROR_HTTP, "SSL certificate is invalid"); else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED)) - git_error_set(GIT_ERROR_NET, "certificate revocation check failed"); + git_error_set(GIT_ERROR_HTTP, "certificate revocation check failed"); else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED)) - git_error_set(GIT_ERROR_NET, "SSL certificate was revoked"); + git_error_set(GIT_ERROR_HTTP, "SSL certificate was revoked"); else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR)) - git_error_set(GIT_ERROR_NET, "security libraries could not be loaded"); + git_error_set(GIT_ERROR_HTTP, "security libraries could not be loaded"); else - git_error_set(GIT_ERROR_NET, "unknown security error %lu", status); + git_error_set(GIT_ERROR_HTTP, "unknown security error %lu", status); } static int winhttp_connect( @@ -741,12 +749,12 @@ static int winhttp_connect( t->connection = NULL; /* Prepare port */ - if (git__strntol32(&port, t->connection_data.port, - strlen(t->connection_data.port), NULL, 10) < 0) + if (git__strntol32(&port, t->server.url.port, + strlen(t->server.url.port), NULL, 10) < 0) return -1; /* Prepare host */ - if (git__utf8_to_16_alloc(&wide_host, t->connection_data.host) < 0) { + if (git__utf8_to_16_alloc(&wide_host, t->server.url.host) < 0) { git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); return -1; } @@ -824,13 +832,18 @@ on_error: return error; } -static int do_send_request(winhttp_stream *s, size_t len, int ignore_length) +static int do_send_request(winhttp_stream *s, size_t len, bool chunked) { int attempts; bool success; + if (len > DWORD_MAX) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return -1; + } + for (attempts = 0; attempts < 5; attempts++) { - if (ignore_length) { + if (chunked) { success = WinHttpSendRequest(s->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, @@ -839,7 +852,7 @@ static int do_send_request(winhttp_stream *s, size_t len, int ignore_length) success = WinHttpSendRequest(s->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, - len, 0); + (DWORD)len, 0); } if (success || GetLastError() != (DWORD)SEC_E_BUFFER_TOO_SMALL) @@ -849,13 +862,13 @@ static int do_send_request(winhttp_stream *s, size_t len, int ignore_length) return success ? 0 : -1; } -static int send_request(winhttp_stream *s, size_t len, int ignore_length) +static int send_request(winhttp_stream *s, size_t len, bool chunked) { int request_failed = 0, cert_valid = 1, error = 0; DWORD ignore_flags; git_error_clear(); - if ((error = do_send_request(s, len, ignore_length)) < 0) { + if ((error = do_send_request(s, len, chunked)) < 0) { if (GetLastError() != ERROR_WINHTTP_SECURE_FAILURE) { git_error_set(GIT_ERROR_OS, "failed to send request"); return -1; @@ -884,12 +897,65 @@ static int send_request(winhttp_stream *s, size_t len, int ignore_length) return -1; } - if ((error = do_send_request(s, len, ignore_length)) < 0) + if ((error = do_send_request(s, len, chunked)) < 0) git_error_set(GIT_ERROR_OS, "failed to send request with unchecked certificate"); return error; } +static int acquire_credentials( + HINTERNET request, + winhttp_server *server, + const char *url_str, + git_credential_acquire_cb cred_cb, + void *cred_cb_payload) +{ + int allowed_types; + int error = 1; + + if (parse_unauthorized_response(&allowed_types, &server->auth_mechanisms, request) < 0) + return -1; + + if (allowed_types) { + git_credential_free(server->cred); + server->cred = NULL; + + /* Start with URL-specified credentials, if there were any. */ + if (!server->url_cred_presented && server->url.username && server->url.password) { + error = acquire_url_cred(&server->cred, allowed_types, server->url.username, server->url.password); + server->url_cred_presented = 1; + + if (error < 0) + return error; + } + + /* Next use the user-defined callback, if there is one. */ + if (error > 0 && cred_cb) { + error = cred_cb(&server->cred, url_str, server->url.username, allowed_types, cred_cb_payload); + + /* Treat GIT_PASSTHROUGH as though git_credential_acquire_cb isn't set */ + if (error == GIT_PASSTHROUGH) + error = 1; + else if (error < 0) + return error; + } + + /* Finally, invoke the fallback default credential lookup. */ + if (error > 0) { + error = acquire_fallback_cred(&server->cred, url_str, allowed_types); + + if (error < 0) + return error; + } + } + + /* + * No error occurred but we could not find appropriate credentials. + * This behaves like a pass-through. + */ + return error; +} + static int winhttp_stream_read( git_smart_subtransport_stream *stream, char *buffer, @@ -905,7 +971,7 @@ static int winhttp_stream_read( replay: /* Enforce a reasonable cap on the number of replays */ if (replay_count++ >= GIT_HTTP_REPLAY_MAX) { - git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays"); + git_error_set(GIT_ERROR_HTTP, "too many redirects or authentication replays"); return -1; } @@ -920,7 +986,7 @@ replay: if (!s->sent_request) { - if ((error = send_request(s, s->post_body_len, 0)) < 0) + if ((error = send_request(s, s->post_body_len, false)) < 0) return error; s->sent_request = 1; @@ -956,6 +1022,7 @@ replay: } buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE); + GIT_ERROR_CHECK_ALLOC(buffer); while (len > 0) { DWORD bytes_written; @@ -1014,7 +1081,8 @@ replay: HTTP_STATUS_REDIRECT == status_code || (HTTP_STATUS_REDIRECT_METHOD == status_code && get_verb == s->verb) || - HTTP_STATUS_REDIRECT_KEEP_VERB == status_code)) { + HTTP_STATUS_REDIRECT_KEEP_VERB == status_code || + HTTP_STATUS_PERMANENT_REDIRECT == status_code)) { /* Check for Windows 7. This workaround is only necessary on * Windows Vista and earlier. Windows 7 is version 6.1. */ @@ -1062,7 +1130,7 @@ replay: if (!git__prefixcmp_icase(location8, prefix_https)) { /* Upgrade to secure connection; disconnect and start over */ - if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0) { + if (git_net_url_apply_redirect(&t->server.url, location8, s->service_url) < 0) { git__free(location8); return -1; } @@ -1077,72 +1145,39 @@ replay: goto replay; } - /* Handle proxy authentication failures */ - if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) { - int allowed_types; - - if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanisms) < 0) - return -1; - - /* TODO: extract the username from the url, no payload? */ - if (t->owner->proxy.credentials) { - int cred_error = 1; - cred_error = t->owner->proxy.credentials(&t->proxy_cred, t->owner->proxy.url, NULL, allowed_types, t->owner->proxy.payload); - - if (cred_error < 0) - return cred_error; - } - - winhttp_stream_close(s); - goto replay; - } - /* Handle authentication failures */ - if (HTTP_STATUS_DENIED == status_code && get_verb == s->verb) { - int allowed_types; + if (status_code == HTTP_STATUS_DENIED) { + int error = acquire_credentials(s->request, + &t->server, + t->owner->url, + t->owner->cred_acquire_cb, + t->owner->cred_acquire_payload); - if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanisms) < 0) - return -1; + if (error < 0) { + return error; + } else if (!error) { + assert(t->server.cred); + winhttp_stream_close(s); + goto replay; + } + } else if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) { + int error = acquire_credentials(s->request, + &t->proxy, + t->owner->proxy.url, + t->owner->proxy.credentials, + t->owner->proxy.payload); - if (allowed_types) { - int cred_error = 1; - - git_cred_free(t->cred); - t->cred = NULL; - /* Start with the user-supplied credential callback, if present */ - if (t->owner->cred_acquire_cb) { - cred_error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, - t->connection_data.user, allowed_types, t->owner->cred_acquire_payload); - - /* Treat GIT_PASSTHROUGH as though git_cred_acquire_cb isn't set */ - if (cred_error == GIT_PASSTHROUGH) - cred_error = 1; - else if (cred_error < 0) - return cred_error; - } - - /* Invoke the fallback credentials acquisition callback if necessary */ - if (cred_error > 0) { - cred_error = fallback_cred_acquire_cb(&t->cred, t->owner->url, - t->connection_data.user, allowed_types, NULL); - - if (cred_error < 0) - return cred_error; - } - - if (!cred_error) { - assert(t->cred); - - winhttp_stream_close(s); - - /* Successfully acquired a credential */ - goto replay; - } + if (error < 0) { + return error; + } else if (!error) { + assert(t->proxy.cred); + winhttp_stream_close(s); + goto replay; } } if (HTTP_STATUS_OK != status_code) { - git_error_set(GIT_ERROR_NET, "request failed with status code: %lu", status_code); + git_error_set(GIT_ERROR_HTTP, "request failed with status code: %lu", status_code); return -1; } @@ -1169,7 +1204,7 @@ replay: } if (wcscmp(expected_content_type, content_type)) { - git_error_set(GIT_ERROR_NET, "received unexpected content-type"); + git_error_set(GIT_ERROR_HTTP, "received unexpected content-type"); return -1; } @@ -1204,11 +1239,11 @@ static int winhttp_stream_write_single( /* This implementation of write permits only a single call. */ if (s->sent_request) { - git_error_set(GIT_ERROR_NET, "subtransport configured for only one write"); + git_error_set(GIT_ERROR_HTTP, "subtransport configured for only one write"); return -1; } - if ((error = send_request(s, len, 0)) < 0) + if ((error = send_request(s, len, false)) < 0) return error; s->sent_request = 1; @@ -1235,12 +1270,12 @@ static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch) if (RPC_S_OK != status && RPC_S_UUID_LOCAL_ONLY != status && RPC_S_UUID_NO_ADDRESS != status) { - git_error_set(GIT_ERROR_NET, "unable to generate name for temp file"); + git_error_set(GIT_ERROR_HTTP, "unable to generate name for temp file"); return -1; } if (buffer_len_cch < UUID_LENGTH_CCH + 1) { - git_error_set(GIT_ERROR_NET, "buffer too small for name of temp file"); + git_error_set(GIT_ERROR_HTTP, "buffer too small for name of temp file"); return -1; } @@ -1347,7 +1382,7 @@ static int winhttp_stream_write_chunked( return -1; } - if ((error = send_request(s, 0, 1)) < 0) + if ((error = send_request(s, 0, true)) < 0) return error; s->sent_request = 1; @@ -1370,8 +1405,10 @@ static int winhttp_stream_write_chunked( /* Append as much to the buffer as we can */ int count = (int)min(CACHED_POST_BODY_BUF_SIZE - s->chunk_buffer_len, len); - if (!s->chunk_buffer) + if (!s->chunk_buffer) { s->chunk_buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE); + GIT_ERROR_CHECK_ALLOC(s->chunk_buffer); + } memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count); s->chunk_buffer_len += count; @@ -1496,7 +1533,7 @@ static int winhttp_action( int ret = -1; if (!t->connection) - if ((ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0 || + if ((ret = git_net_url_parse(&t->server.url, url)) < 0 || (ret = winhttp_connect(t)) < 0) return ret; @@ -1538,24 +1575,17 @@ static int winhttp_close(git_smart_subtransport *subtransport) { winhttp_subtransport *t = (winhttp_subtransport *)subtransport; - gitno_connection_data_free_ptrs(&t->connection_data); - memset(&t->connection_data, 0x0, sizeof(gitno_connection_data)); - gitno_connection_data_free_ptrs(&t->proxy_connection_data); - memset(&t->proxy_connection_data, 0x0, sizeof(gitno_connection_data)); + git_net_url_dispose(&t->server.url); + git_net_url_dispose(&t->proxy.url); - if (t->cred) { - t->cred->free(t->cred); - t->cred = NULL; + if (t->server.cred) { + t->server.cred->free(t->server.cred); + t->server.cred = NULL; } - if (t->proxy_cred) { - t->proxy_cred->free(t->proxy_cred); - t->proxy_cred = NULL; - } - - if (t->url_cred) { - t->url_cred->free(t->url_cred); - t->url_cred = NULL; + if (t->proxy.cred) { + t->proxy.cred->free(t->proxy.cred); + t->proxy.cred = NULL; } return winhttp_close_connection(t); diff --git a/src/tree-cache.c b/src/tree-cache.c index d3ba864eb..04d86fd36 100644 --- a/src/tree-cache.c +++ b/src/tree-cache.c @@ -120,12 +120,14 @@ static int read_tree_internal(git_tree_cache **out, /* Parse children: */ if (tree->children_count > 0) { - unsigned int i; + size_t i, bufsize; - tree->children = git_pool_malloc(pool, tree->children_count * sizeof(git_tree_cache *)); + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&bufsize, tree->children_count, sizeof(git_tree_cache*)); + + tree->children = git_pool_malloc(pool, bufsize); GIT_ERROR_CHECK_ALLOC(tree->children); - memset(tree->children, 0x0, tree->children_count * sizeof(git_tree_cache *)); + memset(tree->children, 0x0, bufsize); for (i = 0; i < tree->children_count; ++i) { if (read_tree_internal(&tree->children[i], &buffer, buffer_end, pool) < 0) @@ -160,7 +162,7 @@ int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer static int read_tree_recursive(git_tree_cache *cache, const git_tree *tree, git_pool *pool) { git_repository *repo; - size_t i, j, nentries, ntrees; + size_t i, j, nentries, ntrees, alloc_size; int error; repo = git_tree_owner(tree); @@ -182,8 +184,10 @@ static int read_tree_recursive(git_tree_cache *cache, const git_tree *tree, git_ ntrees++; } + GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_size, ntrees, sizeof(git_tree_cache *)); + cache->children_count = ntrees; - cache->children = git_pool_mallocz(pool, ntrees * sizeof(git_tree_cache *)); + cache->children = git_pool_mallocz(pool, alloc_size); GIT_ERROR_CHECK_ALLOC(cache->children); j = 0; @@ -232,11 +236,14 @@ int git_tree_cache_read_tree(git_tree_cache **out, const git_tree *tree, git_poo int git_tree_cache_new(git_tree_cache **out, const char *name, git_pool *pool) { - size_t name_len; + size_t name_len, alloc_size; git_tree_cache *tree; name_len = strlen(name); - tree = git_pool_malloc(pool, sizeof(git_tree_cache) + name_len + 1); + + GIT_ERROR_CHECK_ALLOC_ADD3(&alloc_size, sizeof(git_tree_cache), name_len, 1); + + tree = git_pool_malloc(pool, alloc_size); GIT_ERROR_CHECK_ALLOC(tree); memset(tree, 0x0, sizeof(git_tree_cache)); diff --git a/src/tree.c b/src/tree.c index 466180451..48468dff6 100644 --- a/src/tree.c +++ b/src/tree.c @@ -10,7 +10,7 @@ #include "commit.h" #include "git2/repository.h" #include "git2/object.h" -#include "fileops.h" +#include "futils.h" #include "tree-cache.h" #include "index.h" @@ -340,11 +340,11 @@ size_t git_tree_entrycount(const git_tree *tree) return tree->entries.size; } -unsigned int git_treebuilder_entrycount(git_treebuilder *bld) +size_t git_treebuilder_entrycount(git_treebuilder *bld) { assert(bld); - return git_strmap_num_entries(bld->map); + return git_strmap_size(bld->map); } static int tree_error(const char *str, const char *path) @@ -479,7 +479,7 @@ static int check_entry(git_repository *repo, const char *filename, const git_oid if (!valid_entry_name(repo, filename)) return tree_error("failed to insert entry: invalid name for a tree entry", filename); - if (git_oid_iszero(id)) + if (git_oid_is_zero(id)) return tree_error("failed to insert entry: invalid null OID", filename); if (filemode != GIT_FILEMODE_COMMIT && @@ -507,8 +507,7 @@ static int append_entry( entry->attr = (uint16_t)filemode; - git_strmap_insert(bld->map, entry->filename, entry, &error); - if (error < 0) { + if ((error = git_strmap_set(bld->map, entry->filename, entry)) < 0) { git_tree_entry_free(entry); git_error_set(GIT_ERROR_TREE, "failed to append entry %s to the tree builder", filename); return -1; @@ -688,7 +687,7 @@ int git_treebuilder_new( bld->repo = repo; - if (git_strmap_alloc(&bld->map) < 0) { + if (git_strmap_new(&bld->map) < 0) { git__free(bld); return -1; } @@ -723,24 +722,19 @@ int git_treebuilder_insert( { git_tree_entry *entry; int error; - size_t pos; assert(bld && id && filename); if ((error = check_entry(bld->repo, filename, id, filemode)) < 0) return error; - pos = git_strmap_lookup_index(bld->map, filename); - if (git_strmap_valid_index(bld->map, pos)) { - entry = git_strmap_value_at(bld->map, pos); + if ((entry = git_strmap_get(bld->map, filename)) != NULL) { git_oid_cpy((git_oid *) entry->oid, id); } else { entry = alloc_entry(filename, strlen(filename), id); GIT_ERROR_CHECK_ALLOC(entry); - git_strmap_insert(bld->map, entry->filename, entry, &error); - - if (error < 0) { + if ((error = git_strmap_set(bld->map, entry->filename, entry)) < 0) { git_tree_entry_free(entry); git_error_set(GIT_ERROR_TREE, "failed to insert %s", filename); return -1; @@ -757,16 +751,8 @@ int git_treebuilder_insert( static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) { - git_tree_entry *entry = NULL; - size_t pos; - assert(bld && filename); - - pos = git_strmap_lookup_index(bld->map, filename); - if (git_strmap_valid_index(bld->map, pos)) - entry = git_strmap_value_at(bld->map, pos); - - return entry; + return git_strmap_get(bld->map, filename); } const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename) @@ -811,7 +797,7 @@ int git_treebuilder_write_with_buffer(git_oid *oid, git_treebuilder *bld, git_bu git_buf_clear(tree); - entrycount = git_strmap_num_entries(bld->map); + entrycount = git_strmap_size(bld->map); if ((error = git_vector_init(&entries, entrycount, entry_sort_cmp)) < 0) goto out; @@ -848,7 +834,7 @@ out: return error; } -void git_treebuilder_filter( +int git_treebuilder_filter( git_treebuilder *bld, git_treebuilder_filter_cb filter, void *payload) @@ -864,9 +850,11 @@ void git_treebuilder_filter( git_tree_entry_free(entry); } }); + + return 0; } -void git_treebuilder_clear(git_treebuilder *bld) +int git_treebuilder_clear(git_treebuilder *bld) { git_tree_entry *e; @@ -874,6 +862,8 @@ void git_treebuilder_clear(git_treebuilder *bld) git_strmap_foreach_value(bld->map, e, git_tree_entry_free(e)); git_strmap_clear(bld->map); + + return 0; } void git_treebuilder_free(git_treebuilder *bld) diff --git a/src/unix/map.c b/src/unix/map.c index 1ebbced5c..7f9076e19 100644 --- a/src/unix/map.c +++ b/src/unix/map.c @@ -32,7 +32,7 @@ int git__mmap_alignment(size_t *alignment) return git__page_size(alignment); } -int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) { int mprot = PROT_READ; int mflag = 0; diff --git a/src/unix/posix.h b/src/unix/posix.h index f2fffd5c9..4fa725013 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -33,7 +33,7 @@ typedef int GIT_SOCKET; # define st_atime_nsec st_atim.tv_nsec # define st_mtime_nsec st_mtim.tv_nsec # define st_ctime_nsec st_ctim.tv_nsec -#elif !defined(GIT_USE_STAT_MTIME_NSEC) && defined(GIT_USE_NEC) +#elif !defined(GIT_USE_STAT_MTIME_NSEC) && defined(GIT_USE_NSEC) # error GIT_USE_NSEC defined but unknown struct stat nanosecond type #endif @@ -59,7 +59,7 @@ GIT_INLINE(int) p_fsync(int fd) #define p_strcasecmp(s1, s2) strcasecmp(s1, s2) #define p_strncasecmp(s1, s2, c) strncasecmp(s1, s2, c) #define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) -#define p_snprintf(b, c, ...) snprintf(b, c, __VA_ARGS__) +#define p_snprintf snprintf #define p_mkstemp(p) mkstemp(p) #define p_chdir(p) chdir(p) #define p_chmod(p,m) chmod(p, m) @@ -89,14 +89,4 @@ GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2]) # define p_futimes futimes #endif -#ifdef GIT_USE_REGCOMP_L -#include -GIT_INLINE(int) p_regcomp(regex_t *preg, const char *pattern, int cflags) -{ - return regcomp_l(preg, pattern, cflags, (locale_t) 0); -} -#else -# define p_regcomp regcomp -#endif - #endif diff --git a/src/userdiff.h b/src/userdiff.h index 91c1f42dc..c9a80d712 100644 --- a/src/userdiff.h +++ b/src/userdiff.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_userdiff_h__ #define INCLUDE_userdiff_h__ +#include "regexp.h" + /* * This file isolates the built in diff driver function name patterns. * Most of these patterns are taken from Git (with permission from the @@ -29,7 +31,7 @@ typedef struct { #define PATTERNS(NAME, FN_PATS, WORD_PAT) \ { NAME, FN_PATS, WORD_PAT WORD_DEFAULT, 0 } #define IPATTERN(NAME, FN_PATS, WORD_PAT) \ - { NAME, FN_PATS, WORD_PAT WORD_DEFAULT, REG_ICASE } + { NAME, FN_PATS, WORD_PAT WORD_DEFAULT, GIT_REGEXP_ICASE } /* * The table of diff driver patterns diff --git a/src/util.c b/src/util.c index 984a3d780..859e0a82b 100644 --- a/src/util.c +++ b/src/util.c @@ -192,7 +192,7 @@ int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const cha tmp_int = tmp_long & 0xFFFFFFFF; if (tmp_int != tmp_long) { - int len = tmp_endptr - nptr; + int len = (int)(tmp_endptr - nptr); git_error_set(GIT_ERROR_INVALID, "failed to convert: '%.*s' is too large", len, nptr); return -1; } @@ -204,13 +204,6 @@ int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const cha return error; } -int git__strcmp(const char *a, const char *b) -{ - while (*a && *b && *a == *b) - ++a, ++b; - return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b); -} - int git__strcasecmp(const char *a, const char *b) { while (*a && *b && git__tolower(*a) == git__tolower(*b)) @@ -240,15 +233,6 @@ int git__strcasesort_cmp(const char *a, const char *b) return cmp; } -int git__strncmp(const char *a, const char *b, size_t sz) -{ - while (sz && *a && *b && *a == *b) - --sz, ++a, ++b; - if (!sz) - return 0; - return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b); -} - int git__strncasecmp(const char *a, const char *b, size_t sz) { int al, bl; @@ -301,7 +285,18 @@ GIT_INLINE(int) prefixcmp(const char *str, size_t str_n, const char *prefix, boo int git__prefixcmp(const char *str, const char *prefix) { - return prefixcmp(str, SIZE_MAX, prefix, false); + unsigned char s, p; + + while (1) { + p = *prefix++; + s = *str++; + + if (!p) + return 0; + + if (s != p) + return s - p; + } } int git__prefixncmp(const char *str, size_t str_n, const char *prefix) @@ -724,6 +719,37 @@ static int GIT_STDLIB_CALL git__qsort_r_glue_cmp( } #endif + +#if !defined(HAVE_QSORT_R_BSD) && \ + !defined(HAVE_QSORT_R_GNU) && \ + !defined(HAVE_QSORT_S) +static void swap(uint8_t *a, uint8_t *b, size_t elsize) +{ + char tmp[256]; + + while (elsize) { + size_t n = elsize < sizeof(tmp) ? elsize : sizeof(tmp); + memcpy(tmp, a + elsize - n, n); + memcpy(a + elsize - n, b + elsize - n, n); + memcpy(b + elsize - n, tmp, n); + elsize -= n; + } +} + +static void insertsort( + void *els, size_t nel, size_t elsize, + git__sort_r_cmp cmp, void *payload) +{ + uint8_t *base = els; + uint8_t *end = base + nel * elsize; + uint8_t *i, *j; + + for (i = base + elsize; i < end; i += elsize) + for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) + swap(j, j - elsize, elsize); +} +#endif + void git__qsort_r( void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload) { @@ -736,33 +762,10 @@ void git__qsort_r( git__qsort_r_glue glue = { cmp, payload }; qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); #else - git__insertsort_r(els, nel, elsize, NULL, cmp, payload); + insertsort(els, nel, elsize, cmp, payload); #endif } -void git__insertsort_r( - void *els, size_t nel, size_t elsize, void *swapel, - git__sort_r_cmp cmp, void *payload) -{ - uint8_t *base = els; - uint8_t *end = base + nel * elsize; - uint8_t *i, *j; - bool freeswap = !swapel; - - if (freeswap) - swapel = git__malloc(elsize); - - for (i = base + elsize; i < end; i += elsize) - for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) { - memcpy(swapel, j, elsize); - memcpy(j, j - elsize, elsize); - memcpy(j - elsize, swapel, elsize); - } - - if (freeswap) - git__free(swapel); -} - /* * git__utf8_iterate is taken from the utf8proc project, * http://www.public-software-group.org/utf8proc @@ -807,23 +810,23 @@ static const int8_t utf8proc_utf8class[256] = { 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0 }; -int git__utf8_charlen(const uint8_t *str, int str_len) +int git__utf8_charlen(const uint8_t *str, size_t str_len) { - int length, i; + size_t length, i; length = utf8proc_utf8class[str[0]]; if (!length) return -1; - if (str_len >= 0 && length > str_len) - return -str_len; + if (str_len > 0 && length > str_len) + return -1; for (i = 1; i < length; i++) { if ((str[i] & 0xC0) != 0x80) - return -i; + return -1; } - return length; + return (int)length; } int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst) @@ -864,11 +867,6 @@ int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst) return length; } -double git_time_monotonic(void) -{ - return git__timer(); -} - size_t git__utf8_valid_buf_length(const uint8_t *str, size_t str_len) { size_t offset = 0; diff --git a/src/util.h b/src/util.h index 4314295f1..b49850d23 100644 --- a/src/util.h +++ b/src/util.h @@ -30,6 +30,17 @@ # define max(a,b) ((a) > (b) ? (a) : (b)) #endif +#if defined(__GNUC__) +# define GIT_CONTAINER_OF(ptr, type, member) \ + __builtin_choose_expr( \ + __builtin_offsetof(type, member) == 0 && \ + __builtin_types_compatible_p(typeof(&((type *) 0)->member), typeof(ptr)), \ + ((type *) (ptr)), \ + (void)0) +#else +# define GIT_CONTAINER_OF(ptr, type, member) (type *)(ptr) +#endif + #define GIT_DATE_RFC2822_SZ 32 /** @@ -126,10 +137,6 @@ extern void git__tsort_r( extern void git__qsort_r( void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload); -extern void git__insertsort_r( - void *els, size_t nel, size_t elsize, void *swapel, - git__sort_r_cmp cmp, void *payload); - /** * @param position If non-NULL, this will be set to the position where the * element is or would be inserted if not found. @@ -150,12 +157,13 @@ extern int git__bsearch_r( void *payload, size_t *position); +#define git__strcmp strcmp +#define git__strncmp strncmp + extern int git__strcmp_cb(const void *a, const void *b); extern int git__strcasecmp_cb(const void *a, const void *b); -extern int git__strcmp(const char *a, const char *b); extern int git__strcasecmp(const char *a, const char *b); -extern int git__strncmp(const char *a, const char *b, size_t sz); extern int git__strncasecmp(const char *a, const char *b, size_t sz); extern int git__strcasesort_cmp(const char *a, const char *b); @@ -349,22 +357,9 @@ GIT_INLINE(void) git__memzero(void *data, size_t size) GIT_INLINE(double) git__timer(void) { - /* We need the initial tick count to detect if the tick - * count has rolled over. */ - static DWORD initial_tick_count = 0; - - /* GetTickCount returns the number of milliseconds that have + /* GetTickCount64 returns the number of milliseconds that have * elapsed since the system was started. */ - DWORD count = GetTickCount(); - - if(initial_tick_count == 0) { - initial_tick_count = count; - } else if (count < initial_tick_count) { - /* The tick count has rolled over - adjust for it. */ - count = (0xFFFFFFFF - initial_tick_count) + count; - } - - return (double) count / (double) 1000; + return (double) GetTickCount64() / (double) 1000; } #elif __APPLE__ diff --git a/src/wildmatch.c b/src/wildmatch.c new file mode 100644 index 000000000..a894e4841 --- /dev/null +++ b/src/wildmatch.c @@ -0,0 +1,320 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + * + * Do shell-style pattern matching for ?, \, [], and * characters. + * It is 8bit clean. + * + * Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. + * Rich $alz is now . + * + * Modified by Wayne Davison to special-case '/' matching, to make '**' + * work differently than '*', and to fix the character-class code. + * + * Imported from git.git. + */ + +#include "wildmatch.h" + +#define GIT_SPACE 0x01 +#define GIT_DIGIT 0x02 +#define GIT_ALPHA 0x04 +#define GIT_GLOB_SPECIAL 0x08 +#define GIT_REGEX_SPECIAL 0x10 +#define GIT_PATHSPEC_MAGIC 0x20 +#define GIT_CNTRL 0x40 +#define GIT_PUNCT 0x80 + +enum { + S = GIT_SPACE, + A = GIT_ALPHA, + D = GIT_DIGIT, + G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ + R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ + P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */ + X = GIT_CNTRL, + U = GIT_PUNCT, + Z = GIT_CNTRL | GIT_SPACE +}; + +static const unsigned char sane_ctype[256] = { + X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */ + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */ + S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */ + D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */ + P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ + A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */ + P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ + A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */ + /* Nothing in the 128.. range */ +}; + +#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) +#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL) + +typedef unsigned char uchar; + +/* What character marks an inverted character class? */ +#define NEGATE_CLASS '!' +#define NEGATE_CLASS2 '^' + +#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \ + && *(class) == *(litmatch) \ + && strncmp((char*)class, litmatch, len) == 0) + +#if defined STDC_HEADERS || !defined isascii +# define ISASCII(c) 1 +#else +# define ISASCII(c) isascii(c) +#endif + +#ifdef isblank +# define ISBLANK(c) (ISASCII(c) && isblank(c)) +#else +# define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#endif + +#ifdef isgraph +# define ISGRAPH(c) (ISASCII(c) && isgraph(c)) +#else +# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c)) +#endif + +#define ISPRINT(c) (ISASCII(c) && isprint(c)) +#define ISDIGIT(c) (ISASCII(c) && isdigit(c)) +#define ISALNUM(c) (ISASCII(c) && isalnum(c)) +#define ISALPHA(c) (ISASCII(c) && isalpha(c)) +#define ISCNTRL(c) (ISASCII(c) && iscntrl(c)) +#define ISLOWER(c) (ISASCII(c) && islower(c)) +#define ISPUNCT(c) (ISASCII(c) && ispunct(c)) +#define ISSPACE(c) (ISASCII(c) && isspace(c)) +#define ISUPPER(c) (ISASCII(c) && isupper(c)) +#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c)) + +/* Match pattern "p" against "text" */ +static int dowild(const uchar *p, const uchar *text, unsigned int flags) +{ + uchar p_ch; + const uchar *pattern = p; + + for ( ; (p_ch = *p) != '\0'; text++, p++) { + int matched, match_slash, negated; + uchar t_ch, prev_ch; + if ((t_ch = *text) == '\0' && p_ch != '*') + return WM_ABORT_ALL; + if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) + t_ch = tolower(t_ch); + if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) + p_ch = tolower(p_ch); + switch (p_ch) { + case '\\': + /* Literal match with following character. Note that the test + * in "default" handles the p[1] == '\0' failure case. */ + p_ch = *++p; + /* FALLTHROUGH */ + default: + if (t_ch != p_ch) + return WM_NOMATCH; + continue; + case '?': + /* Match anything but '/'. */ + if ((flags & WM_PATHNAME) && t_ch == '/') + return WM_NOMATCH; + continue; + case '*': + if (*++p == '*') { + const uchar *prev_p = p - 2; + while (*++p == '*') {} + if (!(flags & WM_PATHNAME)) + /* without WM_PATHNAME, '*' == '**' */ + match_slash = 1; + else if ((prev_p < pattern || *prev_p == '/') && + (*p == '\0' || *p == '/' || + (p[0] == '\\' && p[1] == '/'))) { + /* + * Assuming we already match 'foo/' and are at + * , just assume it matches + * nothing and go ahead match the rest of the + * pattern with the remaining string. This + * helps make foo/<*><*>/bar (<> because + * otherwise it breaks C comment syntax) match + * both foo/bar and foo/a/bar. + */ + if (p[0] == '/' && + dowild(p + 1, text, flags) == WM_MATCH) + return WM_MATCH; + match_slash = 1; + } else /* WM_PATHNAME is set */ + match_slash = 0; + } else + /* without WM_PATHNAME, '*' == '**' */ + match_slash = flags & WM_PATHNAME ? 0 : 1; + if (*p == '\0') { + /* Trailing "**" matches everything. Trailing "*" matches + * only if there are no more slash characters. */ + if (!match_slash) { + if (strchr((char*)text, '/') != NULL) + return WM_NOMATCH; + } + return WM_MATCH; + } else if (!match_slash && *p == '/') { + /* + * _one_ asterisk followed by a slash + * with WM_PATHNAME matches the next + * directory + */ + const char *slash = strchr((char*)text, '/'); + if (!slash) + return WM_NOMATCH; + text = (const uchar*)slash; + /* the slash is consumed by the top-level for loop */ + break; + } + while (1) { + if (t_ch == '\0') + break; + /* + * Try to advance faster when an asterisk is + * followed by a literal. We know in this case + * that the string before the literal + * must belong to "*". + * If match_slash is false, do not look past + * the first slash as it cannot belong to '*'. + */ + if (!is_glob_special(*p)) { + p_ch = *p; + if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) + p_ch = tolower(p_ch); + while ((t_ch = *text) != '\0' && + (match_slash || t_ch != '/')) { + if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) + t_ch = tolower(t_ch); + if (t_ch == p_ch) + break; + text++; + } + if (t_ch != p_ch) + return WM_NOMATCH; + } + if ((matched = dowild(p, text, flags)) != WM_NOMATCH) { + if (!match_slash || matched != WM_ABORT_TO_STARSTAR) + return matched; + } else if (!match_slash && t_ch == '/') + return WM_ABORT_TO_STARSTAR; + t_ch = *++text; + } + return WM_ABORT_ALL; + case '[': + p_ch = *++p; +#ifdef NEGATE_CLASS2 + if (p_ch == NEGATE_CLASS2) + p_ch = NEGATE_CLASS; +#endif + /* Assign literal 1/0 because of "matched" comparison. */ + negated = p_ch == NEGATE_CLASS ? 1 : 0; + if (negated) { + /* Inverted character class. */ + p_ch = *++p; + } + prev_ch = 0; + matched = 0; + do { + if (!p_ch) + return WM_ABORT_ALL; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return WM_ABORT_ALL; + if (t_ch == p_ch) + matched = 1; + } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') { + p_ch = *++p; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return WM_ABORT_ALL; + } + if (t_ch <= p_ch && t_ch >= prev_ch) + matched = 1; + else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) { + uchar t_ch_upper = toupper(t_ch); + if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch) + matched = 1; + } + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (p_ch == '[' && p[1] == ':') { + const uchar *s; + int i; + for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/ + if (!p_ch) + return WM_ABORT_ALL; + i = (int)(p - s - 1); + if (i < 0 || p[-1] != ':') { + /* Didn't find ":]", so treat like a normal set. */ + p = s - 2; + p_ch = '['; + if (t_ch == p_ch) + matched = 1; + continue; + } + if (CC_EQ(s,i, "alnum")) { + if (ISALNUM(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "alpha")) { + if (ISALPHA(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "blank")) { + if (ISBLANK(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "cntrl")) { + if (ISCNTRL(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "digit")) { + if (ISDIGIT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "graph")) { + if (ISGRAPH(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "lower")) { + if (ISLOWER(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "print")) { + if (ISPRINT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "punct")) { + if (ISPUNCT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "space")) { + if (ISSPACE(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "upper")) { + if (ISUPPER(t_ch)) + matched = 1; + else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "xdigit")) { + if (ISXDIGIT(t_ch)) + matched = 1; + } else /* malformed [:class:] string */ + return WM_ABORT_ALL; + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (t_ch == p_ch) + matched = 1; + } while (prev_ch = p_ch, (p_ch = *++p) != ']'); + if (matched == negated || + ((flags & WM_PATHNAME) && t_ch == '/')) + return WM_NOMATCH; + continue; + } + } + + return *text ? WM_NOMATCH : WM_MATCH; +} + +/* Match the "pattern" against the "text" string. */ +int wildmatch(const char *pattern, const char *text, unsigned int flags) +{ + return dowild((const uchar*)pattern, (const uchar*)text, flags); +} diff --git a/src/wildmatch.h b/src/wildmatch.h new file mode 100644 index 000000000..44bb575a6 --- /dev/null +++ b/src/wildmatch.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_wildmatch_h__ +#define INCLUDE_wildmatch_h__ + +#include "common.h" + +#define WM_CASEFOLD 1 +#define WM_PATHNAME 2 + +#define WM_NOMATCH 1 +#define WM_MATCH 0 +#define WM_ABORT_ALL -1 +#define WM_ABORT_TO_STARSTAR -2 + +int wildmatch(const char *pattern, const char *text, unsigned int flags); + +#endif diff --git a/src/win32/map.c b/src/win32/map.c index 5769cb869..e2ce737de 100644 --- a/src/win32/map.c +++ b/src/win32/map.c @@ -50,7 +50,7 @@ int git__mmap_alignment(size_t *page_size) return 0; } -int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, off64_t offset) { HANDLE fh = (HANDLE)_get_osfhandle(fd); DWORD alignment = get_allocation_granularity(); @@ -58,8 +58,8 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs DWORD view_prot = 0; DWORD off_low = 0; DWORD off_hi = 0; - git_off_t page_start; - git_off_t page_offset; + off64_t page_start; + off64_t page_offset; GIT_MMAP_VALIDATE(out, len, prot, flags); @@ -99,8 +99,6 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs return -1; } - assert(sizeof(git_off_t) == 8); - off_low = (DWORD)(page_start); off_hi = (DWORD)(page_start >> 32); out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len); diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c index b955b024c..18b43e728 100644 --- a/src/win32/path_w32.c +++ b/src/win32/path_w32.c @@ -25,6 +25,9 @@ #define path__is_unc(p) \ (((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/')) +#define path__startswith_slash(p) \ + ((p)[0] == '\\' || (p)[0] == '/') + GIT_INLINE(int) path__cwd(wchar_t *path, int size) { int len; @@ -140,14 +143,24 @@ int git_win32_path_canonicalize(git_win32_path path) *to = L'\0'; - return (to - path); + if ((to - path) > INT_MAX) { + SetLastError(ERROR_FILENAME_EXCED_RANGE); + return -1; + } + + return (int)(to - path); } int git_win32_path__cwd(wchar_t *out, size_t len) { int cwd_len; - if ((cwd_len = path__cwd(out, len)) < 0) + if (len > INT_MAX) { + errno = ENAMETOOLONG; + return -1; + } + + if ((cwd_len = path__cwd(out, (int)len)) < 0) return -1; /* UNC paths */ @@ -211,7 +224,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) goto on_error; } /* Absolute paths omitting the drive letter */ - else if (src[0] == '\\' || src[0] == '/') { + else if (path__startswith_slash(src)) { if (path__cwd(dest, MAX_PATH) < 0) goto on_error; @@ -247,6 +260,30 @@ on_error: return -1; } +int git_win32_path_relative_from_utf8(git_win32_path out, const char *src) +{ + wchar_t *dest = out, *p; + int len; + + /* Handle absolute paths */ + if (git_path_is_absolute(src) || + path__is_nt_namespace(src) || + path__is_unc(src) || + path__startswith_slash(src)) { + return git_win32_path_from_utf8(out, src); + } + + if ((len = git__utf8_to_16(dest, MAX_PATH, src)) < 0) + return -1; + + for (p = dest; p < (dest + len); p++) { + if (*p == L'/') + *p = L'\\'; + } + + return len; +} + int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) { char *out = dest; diff --git a/src/win32/path_w32.h b/src/win32/path_w32.h index facbced81..dab8b96fa 100644 --- a/src/win32/path_w32.h +++ b/src/win32/path_w32.h @@ -8,39 +8,12 @@ #define INCLUDE_win32_path_w32_h__ #include "common.h" - #include "vector.h" -/* - * Provides a large enough buffer to support Windows paths: MAX_PATH is - * 260, corresponding to a maximum path length of 259 characters plus a - * NULL terminator. Prefixing with "\\?\" adds 4 characters, but if the - * original was a UNC path, then we turn "\\server\share" into - * "\\?\UNC\server\share". So we replace the first two characters with - * 8 characters, a net gain of 6, so the maximum length is MAX_PATH+6. - */ -#define GIT_WIN_PATH_UTF16 MAX_PATH+6 - -/* Maximum size of a UTF-8 Win32 path. We remove the "\\?\" or "\\?\UNC\" - * prefixes for presentation, bringing us back to 259 (non-NULL) - * characters. UTF-8 does have 4-byte sequences, but they are encoded in - * UTF-16 using surrogate pairs, which takes up the space of two characters. - * Two characters in the range U+0800 -> U+FFFF take up more space in UTF-8 - * (6 bytes) than one surrogate pair (4 bytes). - */ -#define GIT_WIN_PATH_UTF8 (259 * 3 + 1) - -/* - * The length of a Windows "shortname", for 8.3 compatibility. - */ -#define GIT_WIN_PATH_SHORTNAME 13 - -/* Win32 path types */ -typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; -typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8]; - /** - * Create a Win32 path (in UCS-2 format) from a UTF-8 string. + * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given + * path is relative, then it will be turned into an absolute path by having + * the current working directory prepended. * * @param dest The buffer to receive the wide string. * @param src The UTF-8 string to convert. @@ -48,6 +21,16 @@ typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8]; */ extern int git_win32_path_from_utf8(git_win32_path dest, const char *src); +/** + * Create a Win32 path (in UCS-2 format) from a UTF-8 string. If the given + * path is relative, then it will not be turned into an absolute path. + * + * @param dest The buffer to receive the wide string. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + */ +extern int git_win32_path_relative_from_utf8(git_win32_path dest, const char *src); + /** * Canonicalize a Win32 UCS-2 path so that it is suitable for delivery to the * Win32 APIs: remove multiple directory separators, squashing to a single one, @@ -55,6 +38,9 @@ extern int git_win32_path_from_utf8(git_win32_path dest, const char *src); * canonical (always backslashes, never forward slashes) and process any * directory entries of '.' or '..'. * + * Note that this is intended to be used on absolute Windows paths, those + * that start with `C:\`, `\\server\share`, `\\?\`, etc. + * * This processes the buffer in place. * * @param path The buffer to process diff --git a/src/win32/posix.h b/src/win32/posix.h index d5ab2e8f5..f115088b4 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -47,7 +47,7 @@ extern int p_chdir(const char* path); extern int p_chmod(const char* path, mode_t mode); extern int p_rmdir(const char* path); extern int p_access(const char* path, mode_t mode); -extern int p_ftruncate(int fd, git_off_t size); +extern int p_ftruncate(int fd, off64_t size); /* p_lstat is almost but not quite POSIX correct. Specifically, the use of * ENOTDIR is wrong, in that it does not mean precisely that a non-directory @@ -60,7 +60,4 @@ extern int p_lstat_posixly(const char *filename, struct stat *buf); extern struct tm * p_localtime_r(const time_t *timer, struct tm *result); extern struct tm * p_gmtime_r(const time_t *timer, struct tm *result); -/* Use the bundled regcomp */ -#define p_regcomp regcomp - #endif diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 91e164348..cacf986e8 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -8,7 +8,7 @@ #include "common.h" #include "../posix.h" -#include "../fileops.h" +#include "../futils.h" #include "path.h" #include "path_w32.h" #include "utf-conv.h" @@ -210,7 +210,7 @@ on_error: * We now take a "git_off_t" rather than "long" because * files may be longer than 2Gb. */ -int p_ftruncate(int fd, git_off_t size) +int p_ftruncate(int fd, off64_t size) { if (size < 0) { errno = EINVAL; @@ -251,9 +251,25 @@ int p_link(const char *old, const char *new) GIT_INLINE(int) unlink_once(const wchar_t *path) { + DWORD error; + if (DeleteFileW(path)) return 0; + if ((error = GetLastError()) == ERROR_ACCESS_DENIED) { + WIN32_FILE_ATTRIBUTE_DATA fdata; + if (!GetFileAttributesExW(path, GetFileExInfoStandard, &fdata) || + !(fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || + !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + goto out; + + if (RemoveDirectoryW(path)) + return 0; + } + +out: + SetLastError(error); + if (last_error_retryable()) return GIT_RETRY; @@ -398,18 +414,44 @@ int p_readlink(const char *path, char *buf, size_t bufsiz) return (int)bufsiz; } +static bool target_is_dir(const char *target, const char *path) +{ + git_buf resolved = GIT_BUF_INIT; + git_win32_path resolved_w; + bool isdir = true; + + if (git_path_is_absolute(target)) + git_win32_path_from_utf8(resolved_w, target); + else if (git_path_dirname_r(&resolved, path) < 0 || + git_path_apply_relative(&resolved, target) < 0 || + git_win32_path_from_utf8(resolved_w, resolved.ptr) < 0) + goto out; + + isdir = GetFileAttributesW(resolved_w) & FILE_ATTRIBUTE_DIRECTORY; + +out: + git_buf_dispose(&resolved); + return isdir; +} + int p_symlink(const char *target, const char *path) { git_win32_path target_w, path_w; DWORD dwFlags; + /* + * Convert both target and path to Windows-style paths. Note that we do + * not want to use `git_win32_path_from_utf8` for converting the target, + * as that function will automatically pre-pend the current working + * directory in case the path is not absolute. As Git will instead use + * relative symlinks, this is not someting we want. + */ if (git_win32_path_from_utf8(path_w, path) < 0 || - git__utf8_to_16(target_w, MAX_PATH, target) < 0) + git_win32_path_relative_from_utf8(target_w, target) < 0) return -1; dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; - - if (GetFileAttributesW(target_w) & FILE_ATTRIBUTE_DIRECTORY) + if (target_is_dir(target, path)) dwFlags |= SYMBOLIC_LINK_FLAG_DIRECTORY; if (!CreateSymbolicLinkW(path_w, target_w, dwFlags)) diff --git a/src/win32/precompiled.h b/src/win32/precompiled.h index 851a083d5..314383d31 100644 --- a/src/win32/precompiled.h +++ b/src/win32/precompiled.h @@ -13,8 +13,6 @@ #include #include -#include - #include #include #ifdef GIT_THREADS diff --git a/src/win32/thread.c b/src/win32/thread.c index 2d9600515..42dba7f97 100644 --- a/src/win32/thread.c +++ b/src/win32/thread.c @@ -32,8 +32,6 @@ static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter) thread->result = thread->proc(thread->param); - git__free_tls_data(); - return CLEAN_THREAD_EXIT; } @@ -42,15 +40,15 @@ int git_threads_init(void) HMODULE hModule = GetModuleHandleW(L"kernel32"); if (hModule) { - win32_srwlock_initialize = (win32_srwlock_fn) + win32_srwlock_initialize = (win32_srwlock_fn)(void *) GetProcAddress(hModule, "InitializeSRWLock"); - win32_srwlock_acquire_shared = (win32_srwlock_fn) + win32_srwlock_acquire_shared = (win32_srwlock_fn)(void *) GetProcAddress(hModule, "AcquireSRWLockShared"); - win32_srwlock_release_shared = (win32_srwlock_fn) + win32_srwlock_release_shared = (win32_srwlock_fn)(void *) GetProcAddress(hModule, "ReleaseSRWLockShared"); - win32_srwlock_acquire_exclusive = (win32_srwlock_fn) + win32_srwlock_acquire_exclusive = (win32_srwlock_fn)(void *) GetProcAddress(hModule, "AcquireSRWLockExclusive"); - win32_srwlock_release_exclusive = (win32_srwlock_fn) + win32_srwlock_release_exclusive = (win32_srwlock_fn)(void *) GetProcAddress(hModule, "ReleaseSRWLockExclusive"); } @@ -103,9 +101,6 @@ void git_thread_exit(void *value) { assert(GIT_GLOBAL->current_thread); GIT_GLOBAL->current_thread->result = value; - - git__free_tls_data(); - ExitThread(CLEAN_THREAD_EXIT); } diff --git a/src/win32/w32_buffer.c b/src/win32/w32_buffer.c index 22ea9a10c..b78a7e6f3 100644 --- a/src/win32/w32_buffer.c +++ b/src/win32/w32_buffer.c @@ -25,13 +25,17 @@ int git_buf_put_w(git_buf *buf, const wchar_t *string_w, size_t len_w) int utf8_len, utf8_write_len; size_t new_size; - if (!len_w) + if (!len_w) { return 0; + } else if (len_w > INT_MAX) { + git_error_set_oom(); + return -1; + } assert(string_w); /* Measure the string necessary for conversion */ - if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string_w, len_w, NULL, 0, NULL, NULL)) == 0) + if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, NULL, 0, NULL, NULL)) == 0) return 0; assert(utf8_len > 0); @@ -43,7 +47,7 @@ int git_buf_put_w(git_buf *buf, const wchar_t *string_w, size_t len_w) return -1; if ((utf8_write_len = WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, string_w, len_w, &buf->ptr[buf->size], utf8_len, NULL, NULL)) == 0) + CP_UTF8, WC_ERR_INVALID_CHARS, string_w, (int)len_w, &buf->ptr[buf->size], utf8_len, NULL, NULL)) == 0) return handle_wc_error(); assert(utf8_write_len == utf8_len); diff --git a/src/win32/w32_common.h b/src/win32/w32_common.h new file mode 100644 index 000000000..f9e74b947 --- /dev/null +++ b/src/win32/w32_common.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_win32_w32_common_h__ +#define INCLUDE_win32_w32_common_h__ + +/* + * Provides a large enough buffer to support Windows paths: MAX_PATH is + * 260, corresponding to a maximum path length of 259 characters plus a + * NULL terminator. Prefixing with "\\?\" adds 4 characters, but if the + * original was a UNC path, then we turn "\\server\share" into + * "\\?\UNC\server\share". So we replace the first two characters with + * 8 characters, a net gain of 6, so the maximum length is MAX_PATH+6. + */ +#define GIT_WIN_PATH_UTF16 MAX_PATH+6 + +/* Maximum size of a UTF-8 Win32 path. We remove the "\\?\" or "\\?\UNC\" + * prefixes for presentation, bringing us back to 259 (non-NULL) + * characters. UTF-8 does have 4-byte sequences, but they are encoded in + * UTF-16 using surrogate pairs, which takes up the space of two characters. + * Two characters in the range U+0800 -> U+FFFF take up more space in UTF-8 + * (6 bytes) than one surrogate pair (4 bytes). + */ +#define GIT_WIN_PATH_UTF8 (259 * 3 + 1) + +/* + * The length of a Windows "shortname", for 8.3 compatibility. + */ +#define GIT_WIN_PATH_SHORTNAME 13 + +/* Win32 path types */ +typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16]; +typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8]; + +#endif diff --git a/src/win32/w32_crtdbg_stacktrace.c b/src/win32/w32_crtdbg_stacktrace.c index 49b54f134..cdb5ac1a5 100644 --- a/src/win32/w32_crtdbg_stacktrace.c +++ b/src/win32/w32_crtdbg_stacktrace.c @@ -71,99 +71,6 @@ static bool g_limit_reached = false; /* had allocs after we filled row table */ static unsigned int g_checkpoint_id = 0; /* to better label leak checkpoints */ static bool g_transient_leaks_since_mark = false; /* payload for hook */ -static void *crtdbg__malloc(size_t len, const char *file, int line) -{ - void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static void *crtdbg__calloc(size_t nelem, size_t elsize, const char *file, int line) -{ - void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static char *crtdbg__strdup(const char *str, const char *file, int line) -{ - char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); - if (!ptr) git_error_set_oom(); - return ptr; -} - -static char *crtdbg__strndup(const char *str, size_t n, const char *file, int line) -{ - size_t length = 0, alloclength; - char *ptr; - - length = p_strnlen(str, n); - - if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) || - !(ptr = crtdbg__malloc(alloclength, file, line))) - return NULL; - - if (length) - memcpy(ptr, str, length); - - ptr[length] = '\0'; - - return ptr; -} - -static char *crtdbg__substrdup(const char *start, size_t n, const char *file, int line) -{ - char *ptr; - size_t alloclen; - - if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) || - !(ptr = crtdbg__malloc(alloclen, file, line))) - return NULL; - - memcpy(ptr, start, n); - ptr[n] = '\0'; - return ptr; -} - -static void *crtdbg__realloc(void *ptr, size_t size, const char *file, int line) -{ - void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); - if (!new_ptr) git_error_set_oom(); - return new_ptr; -} - -static void *crtdbg__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line) -{ - size_t newsize; - - return GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize) ? - NULL : _realloc_dbg(ptr, newsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line); -} - -static void *crtdbg__mallocarray(size_t nelem, size_t elsize, const char *file, int line) -{ - return crtdbg__reallocarray(NULL, nelem, elsize, file, line); -} - -static void crtdbg__free(void *ptr) -{ - free(ptr); -} - -int git_win32_crtdbg_init_allocator(git_allocator *allocator) -{ - allocator->gmalloc = crtdbg__malloc; - allocator->gcalloc = crtdbg__calloc; - allocator->gstrdup = crtdbg__strdup; - allocator->gstrndup = crtdbg__strndup; - allocator->gsubstrdup = crtdbg__substrdup; - allocator->grealloc = crtdbg__realloc; - allocator->greallocarray = crtdbg__reallocarray; - allocator->gmallocarray = crtdbg__mallocarray; - allocator->gfree = crtdbg__free; - return 0; -} - /** * Compare function for bsearch on g_cs_index table. */ diff --git a/src/win32/w32_crtdbg_stacktrace.h b/src/win32/w32_crtdbg_stacktrace.h index 7a3deeff4..d65154bab 100644 --- a/src/win32/w32_crtdbg_stacktrace.h +++ b/src/win32/w32_crtdbg_stacktrace.h @@ -43,8 +43,6 @@ * startup. See tests/main.c for an example. */ -int git_win32_crtdbg_init_allocator(git_allocator *allocator); - /** * Initialize our memory leak tracking and de-dup data structures. * This should ONLY be called by git_libgit2_init(). diff --git a/src/win32/w32_stack.c b/src/win32/w32_stack.c index c9d6b1a28..78c78dbbd 100644 --- a/src/win32/w32_stack.c +++ b/src/win32/w32_stack.c @@ -13,11 +13,6 @@ #include "win32/posix.h" #include "hash.h" -/** - * This is supposedly defined in WinBase.h (from Windows.h) but there were linker issues. - */ -USHORT WINAPI RtlCaptureStackBackTrace(ULONG, ULONG, PVOID*, PULONG); - static bool g_win32_stack_initialized = false; static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE; static git_win32__stack__aux_cb_alloc g_aux_cb_alloc = NULL; @@ -81,7 +76,7 @@ int git_win32__stack_compare( } int git_win32__stack_format( - char *pbuf, int buf_len, + char *pbuf, size_t buf_len, const git_win32__stack__raw_data *pdata, const char *prefix, const char *suffix) { @@ -96,10 +91,10 @@ int git_win32__stack_format( } s; IMAGEHLP_LINE64 line; - int buf_used = 0; + size_t buf_used = 0; unsigned int k; char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */ - int detail_len; + size_t detail_len; if (!g_win32_stack_initialized) { git_error_set(GIT_ERROR_INVALID, "git_win32_stack not initialized."); @@ -176,7 +171,7 @@ int git_win32__stack_format( } int git_win32__stack( - char * pbuf, int buf_len, + char * pbuf, size_t buf_len, int skip, const char *prefix, const char *suffix) { diff --git a/src/win32/w32_stack.h b/src/win32/w32_stack.h index 5f0009e0b..c2565c228 100644 --- a/src/win32/w32_stack.h +++ b/src/win32/w32_stack.h @@ -38,7 +38,7 @@ typedef void (*git_win32__stack__aux_cb_alloc)(unsigned int *aux_id); * @param aux_msg A buffer where a formatted message should be written. * @param aux_msg_len The size of the buffer. */ -typedef void (*git_win32__stack__aux_cb_lookup)(unsigned int aux_id, char *aux_msg, unsigned int aux_msg_len); +typedef void (*git_win32__stack__aux_cb_lookup)(unsigned int aux_id, char *aux_msg, size_t aux_msg_len); /** * Register an "aux" data provider to augment our C stacktrace data. @@ -116,7 +116,7 @@ int git_win32__stack_compare( * @param suffix String written after each frame; defaults to "\n". */ int git_win32__stack_format( - char *pbuf, int buf_len, + char *pbuf, size_t buf_len, const git_win32__stack__raw_data *pdata, const char *prefix, const char *suffix); @@ -132,7 +132,7 @@ int git_win32__stack_format( * @param suffix String written after each frame; defaults to "\n". */ int git_win32__stack( - char * pbuf, int buf_len, + char * pbuf, size_t buf_len, int skip, const char *prefix, const char *suffix); diff --git a/src/win32/w32_util.c b/src/win32/w32_util.c index 10e17fcd0..fe4b75bae 100644 --- a/src/win32/w32_util.c +++ b/src/win32/w32_util.c @@ -93,3 +93,34 @@ int git_win32__hidden(bool *out, const char *path) *out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false; return 0; } + +int git_win32__file_attribute_to_stat( + struct stat *st, + const WIN32_FILE_ATTRIBUTE_DATA *attrdata, + const wchar_t *path) +{ + git_win32__stat_init(st, + attrdata->dwFileAttributes, + attrdata->nFileSizeHigh, + attrdata->nFileSizeLow, + attrdata->ftCreationTime, + attrdata->ftLastAccessTime, + attrdata->ftLastWriteTime); + + if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && path) { + git_win32_path target; + + if (git_win32_path_readlink_w(target, path) >= 0) { + st->st_mode = (st->st_mode & ~S_IFMT) | S_IFLNK; + + /* st_size gets the UTF-8 length of the target name, in bytes, + * not counting the NULL terminator */ + if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) { + git_error_set(GIT_ERROR_OS, "could not convert reparse point name for '%ls'", path); + return -1; + } + } + } + + return 0; +} diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h index 3a7942563..d7f9d3da6 100644 --- a/src/win32/w32_util.h +++ b/src/win32/w32_util.h @@ -59,6 +59,11 @@ extern int git_win32__set_hidden(const char *path, bool hidden); */ extern int git_win32__hidden(bool *hidden, const char *path); +extern int git_win32__file_attribute_to_stat( + struct stat *st, + const WIN32_FILE_ATTRIBUTE_DATA *attrdata, + const wchar_t *path); + /** * Converts a FILETIME structure to a struct timespec. * @@ -115,7 +120,7 @@ GIT_INLINE(void) git_win32__stat_init( st->st_uid = 0; st->st_nlink = 1; st->st_mode = mode; - st->st_size = ((git_off_t)nFileSizeHigh << 32) + nFileSizeLow; + st->st_size = ((int64_t)nFileSizeHigh << 32) + nFileSizeLow; st->st_dev = _getdrive() - 1; st->st_rdev = st->st_dev; git_win32__filetime_to_timespec(&ftLastAccessTime, &(st->st_atim)); @@ -136,35 +141,4 @@ GIT_INLINE(void) git_win32__file_information_to_stat( fileinfo->ftLastWriteTime); } -GIT_INLINE(int) git_win32__file_attribute_to_stat( - struct stat *st, - const WIN32_FILE_ATTRIBUTE_DATA *attrdata, - const wchar_t *path) -{ - git_win32__stat_init(st, - attrdata->dwFileAttributes, - attrdata->nFileSizeHigh, - attrdata->nFileSizeLow, - attrdata->ftCreationTime, - attrdata->ftLastAccessTime, - attrdata->ftLastWriteTime); - - if (attrdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && path) { - git_win32_path target; - - if (git_win32_path_readlink_w(target, path) >= 0) { - st->st_mode = (st->st_mode & ~S_IFMT) | S_IFLNK; - - /* st_size gets the UTF-8 length of the target name, in bytes, - * not counting the NULL terminator */ - if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) { - git_error_set(GIT_ERROR_OS, "could not convert reparse point name for '%ls'", path); - return -1; - } - } - } - - return 0; -} - #endif diff --git a/src/worktree.c b/src/worktree.c index 174a10736..74b32f97c 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -34,7 +34,7 @@ int git_worktree_list(git_strarray *wts, git_repository *repo) git_vector worktrees = GIT_VECTOR_INIT; git_buf path = GIT_BUF_INIT; char *worktree; - unsigned i, len; + size_t i, len; int error; assert(wts && repo); @@ -136,11 +136,11 @@ static int open_worktree_dir(git_worktree **out, const char *parent, const char goto out; } - if ((wt->name = git__strdup(name)) == NULL - || (wt->commondir_path = git_worktree__read_link(dir, "commondir")) == NULL - || (wt->gitlink_path = git_worktree__read_link(dir, "gitdir")) == NULL - || (parent && (wt->parent_path = git__strdup(parent)) == NULL) - || (wt->worktree_path = git_path_dirname(wt->gitlink_path)) == NULL) { + if ((wt->name = git__strdup(name)) == NULL || + (wt->commondir_path = git_worktree__read_link(dir, "commondir")) == NULL || + (wt->gitlink_path = git_worktree__read_link(dir, "gitdir")) == NULL || + (parent && (wt->parent_path = git__strdup(parent)) == NULL) || + (wt->worktree_path = git_path_dirname(wt->gitlink_path)) == NULL) { error = -1; goto out; } @@ -149,7 +149,10 @@ static int open_worktree_dir(git_worktree **out, const char *parent, const char goto out; wt->gitdir_path = git_buf_detach(&gitdir); - wt->locked = !!git_worktree_is_locked(NULL, wt); + if ((error = git_worktree_is_locked(NULL, wt)) < 0) + goto out; + wt->locked = !!error; + error = 0; *out = wt; @@ -238,21 +241,21 @@ int git_worktree_validate(const git_worktree *wt) if (!is_worktree_dir(wt->gitdir_path)) { git_error_set(GIT_ERROR_WORKTREE, - "Worktree gitdir ('%s') is not valid", + "worktree gitdir ('%s') is not valid", wt->gitlink_path); return GIT_ERROR; } if (wt->parent_path && !git_path_exists(wt->parent_path)) { git_error_set(GIT_ERROR_WORKTREE, - "Worktree parent directory ('%s') does not exist ", + "worktree parent directory ('%s') does not exist ", wt->parent_path); return GIT_ERROR; } if (!git_path_exists(wt->commondir_path)) { git_error_set(GIT_ERROR_WORKTREE, - "Worktree common directory ('%s') does not exist ", + "worktree common directory ('%s') does not exist ", wt->commondir_path); return GIT_ERROR; } @@ -260,7 +263,7 @@ int git_worktree_validate(const git_worktree *wt) return 0; } -int git_worktree_add_init_options(git_worktree_add_options *opts, +int git_worktree_add_options_init(git_worktree_add_options *opts, unsigned int version) { GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version, @@ -268,6 +271,12 @@ int git_worktree_add_init_options(git_worktree_add_options *opts, return 0; } +int git_worktree_add_init_options(git_worktree_add_options *opts, + unsigned int version) +{ + return git_worktree_add_options_init(opts, version); +} + int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree, const git_worktree_add_options *opts) @@ -290,6 +299,20 @@ int git_worktree_add(git_worktree **out, git_repository *repo, *out = NULL; + if (wtopts.ref) { + if (!git_reference_is_branch(wtopts.ref)) { + git_error_set(GIT_ERROR_WORKTREE, "reference is not a branch"); + err = -1; + goto out; + } + + if (git_branch_is_checked_out(wtopts.ref)) { + git_error_set(GIT_ERROR_WORKTREE, "reference is already checked out"); + err = -1; + goto out; + } + } + /* Create gitdir directory ".git/worktrees/" */ if ((err = git_buf_joinpath(&gitdir, repo->commondir, "worktrees")) < 0) goto out; @@ -342,18 +365,6 @@ int git_worktree_add(git_worktree **out, git_repository *repo, /* Set up worktree reference */ if (wtopts.ref) { - if (!git_reference_is_branch(wtopts.ref)) { - git_error_set(GIT_ERROR_WORKTREE, "reference is not a branch"); - err = -1; - goto out; - } - - if (git_branch_is_checked_out(wtopts.ref)) { - git_error_set(GIT_ERROR_WORKTREE, "reference is already checked out"); - err = -1; - goto out; - } - if ((err = git_reference_dup(&ref, wtopts.ref)) < 0) goto out; } else { @@ -395,20 +406,24 @@ out: int git_worktree_lock(git_worktree *wt, const char *reason) { git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT; - int err; + int error; assert(wt); - if ((err = git_worktree_is_locked(NULL, wt)) < 0) + if ((error = git_worktree_is_locked(NULL, wt)) < 0) goto out; + if (error) { + error = GIT_ELOCKED; + goto out; + } - if ((err = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0) + if ((error = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0) goto out; if (reason) git_buf_attach_notowned(&buf, reason, strlen(reason)); - if ((err = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) + if ((error = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) goto out; wt->locked = 1; @@ -416,16 +431,19 @@ int git_worktree_lock(git_worktree *wt, const char *reason) out: git_buf_dispose(&path); - return err; + return error; } int git_worktree_unlock(git_worktree *wt) { git_buf path = GIT_BUF_INIT; + int error; assert(wt); - if (!git_worktree_is_locked(NULL, wt)) + if ((error = git_worktree_is_locked(NULL, wt)) < 0) + return error; + if (!error) return 1; if (git_buf_joinpath(&path, wt->gitdir_path, "locked") < 0) @@ -446,22 +464,25 @@ int git_worktree_unlock(git_worktree *wt) int git_worktree_is_locked(git_buf *reason, const git_worktree *wt) { git_buf path = GIT_BUF_INIT; - int ret; + int error, locked; assert(wt); if (reason) git_buf_clear(reason); - if ((ret = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0) + if ((error = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0) + goto out; + locked = git_path_exists(path.ptr); + if (locked && reason && + (error = git_futils_readbuffer(reason, path.ptr)) < 0) goto out; - if ((ret = git_path_exists(path.ptr)) && reason) - git_futils_readbuffer(reason, path.ptr); + error = locked; out: git_buf_dispose(&path); - return ret; + return error; } const char *git_worktree_name(const git_worktree *wt) @@ -476,7 +497,7 @@ const char *git_worktree_path(const git_worktree *wt) return wt->worktree_path; } -int git_worktree_prune_init_options( +int git_worktree_prune_options_init( git_worktree_prune_options *opts, unsigned int version) { @@ -485,10 +506,15 @@ int git_worktree_prune_init_options( return 0; } +int git_worktree_prune_init_options(git_worktree_prune_options *opts, + unsigned int version) +{ + return git_worktree_prune_options_init(opts, version); +} + int git_worktree_is_prunable(git_worktree *wt, git_worktree_prune_options *opts) { - git_buf reason = GIT_BUF_INIT; git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT; GIT_ERROR_CHECK_VERSION( @@ -498,21 +524,25 @@ int git_worktree_is_prunable(git_worktree *wt, if (opts) memcpy(&popts, opts, sizeof(popts)); - if ((popts.flags & GIT_WORKTREE_PRUNE_LOCKED) == 0 && - git_worktree_is_locked(&reason, wt)) - { - if (!reason.size) - git_buf_attach_notowned(&reason, "no reason given", 15); - git_error_set(GIT_ERROR_WORKTREE, "Not pruning locked working tree: '%s'", reason.ptr); - git_buf_dispose(&reason); + if ((popts.flags & GIT_WORKTREE_PRUNE_LOCKED) == 0) { + git_buf reason = GIT_BUF_INIT; + int error; - return 0; + if ((error = git_worktree_is_locked(&reason, wt)) < 0) + return error; + + if (error) { + if (!reason.size) + git_buf_attach_notowned(&reason, "no reason given", 15); + git_error_set(GIT_ERROR_WORKTREE, "not pruning locked working tree: '%s'", reason.ptr); + git_buf_dispose(&reason); + return 0; + } } if ((popts.flags & GIT_WORKTREE_PRUNE_VALID) == 0 && - git_worktree_validate(wt) == 0) - { - git_error_set(GIT_ERROR_WORKTREE, "Not pruning valid working tree"); + git_worktree_validate(wt) == 0) { + git_error_set(GIT_ERROR_WORKTREE, "not pruning valid working tree"); return 0; } @@ -544,7 +574,7 @@ int git_worktree_prune(git_worktree *wt, goto out; if (!git_path_exists(path.ptr)) { - git_error_set(GIT_ERROR_WORKTREE, "Worktree gitdir '%s' does not exist", path.ptr); + git_error_set(GIT_ERROR_WORKTREE, "worktree gitdir '%s' does not exist", path.ptr); err = -1; goto out; } @@ -564,7 +594,7 @@ int git_worktree_prune(git_worktree *wt, git_buf_attach(&path, wtpath, 0); if (!git_path_exists(path.ptr)) { - git_error_set(GIT_ERROR_WORKTREE, "Working tree '%s' does not exist", path.ptr); + git_error_set(GIT_ERROR_WORKTREE, "working tree '%s' does not exist", path.ptr); err = -1; goto out; } diff --git a/src/xdiff/xdiffi.c b/src/xdiff/xdiffi.c index 9a7f53808..916295b44 100644 --- a/src/xdiff/xdiffi.c +++ b/src/xdiff/xdiffi.c @@ -36,7 +36,7 @@ #elif defined(__GNUC__) # define XDL_INLINE(type) static __inline__ type #else -#define XDG_INLINE(type) static type +# define XDL_INLINE(type) static type #endif typedef struct s_xdpsplit { diff --git a/src/xdiff/xmerge.c b/src/xdiff/xmerge.c index e6eaf24b5..278cbe124 100644 --- a/src/xdiff/xmerge.c +++ b/src/xdiff/xmerge.c @@ -717,10 +717,22 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, status = 0; if (!xscr1) { result->ptr = xdl_malloc(mf2->size); + if (!result->ptr) { + xdl_free_script(xscr2); + xdl_free_env(&xe1); + xdl_free_env(&xe2); + return -1; + } memcpy(result->ptr, mf2->ptr, mf2->size); result->size = mf2->size; } else if (!xscr2) { result->ptr = xdl_malloc(mf1->size); + if (!result->ptr) { + xdl_free_script(xscr1); + xdl_free_env(&xe1); + xdl_free_env(&xe2); + return -1; + } memcpy(result->ptr, mf1->ptr, mf1->size); result->size = mf1->size; } else { diff --git a/src/xdiff/xpatience.c b/src/xdiff/xpatience.c index cedf39cc3..53b7d5fd1 100644 --- a/src/xdiff/xpatience.c +++ b/src/xdiff/xpatience.c @@ -217,6 +217,9 @@ static struct entry *find_longest_common_sequence(struct hashmap *map) */ int anchor_i = -1; + if (!sequence) + return NULL; + for (entry = map->first; entry; entry = entry->next) { if (!entry->line2 || entry->line2 == NON_UNIQUE) continue; diff --git a/src/zstream.c b/src/zstream.c index fc8bfb868..975ead2f6 100644 --- a/src/zstream.c +++ b/src/zstream.c @@ -77,6 +77,11 @@ bool git_zstream_done(git_zstream *zstream) return (!zstream->in_len && zstream->zerr == Z_STREAM_END); } +bool git_zstream_eos(git_zstream *zstream) +{ + return zstream->zerr == Z_STREAM_END; +} + size_t git_zstream_suggest_output_len(git_zstream *zstream) { if (zstream->in_len > ZSTREAM_BUFFER_SIZE) diff --git a/src/zstream.h b/src/zstream.h index 47ecc1322..db0cc477c 100644 --- a/src/zstream.h +++ b/src/zstream.h @@ -44,6 +44,7 @@ int git_zstream_get_output_chunk( int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream); bool git_zstream_done(git_zstream *zstream); +bool git_zstream_eos(git_zstream *zstream); void git_zstream_reset(git_zstream *zstream); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aaa8ed109..6f8a18ec0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,12 +42,7 @@ ADD_EXECUTABLE(libgit2_clar ${SRC_CLAR} ${SRC_TEST} ${LIBGIT2_OBJECTS}) SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES C_STANDARD 90) SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR}) -IF (${CMAKE_VERSION} VERSION_LESS 2.8.12) - # Already handled by a global INCLUDE_DIRECTORY() -ELSE() - TARGET_INCLUDE_DIRECTORIES(libgit2_clar PRIVATE ../src PUBLIC ../include) -ENDIF() - +TARGET_INCLUDE_DIRECTORIES(libgit2_clar PRIVATE ../src PUBLIC ../include) TARGET_LINK_LIBRARIES(libgit2_clar ${LIBGIT2_LIBS}) IDE_SPLIT_SOURCES(libgit2_clar) @@ -57,9 +52,19 @@ IF (MSVC_IDE) SET_SOURCE_FILES_PROPERTIES("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h") ENDIF () -ADD_TEST(offline "${libgit2_BINARY_DIR}/libgit2_clar" -v -xonline) -ADD_TEST(invasive "${libgit2_BINARY_DIR}/libgit2_clar" -v -score::ftruncate -sfilter::stream::bigfile -sodb::largefiles -siterator::workdir::filesystem_gunk -srepo::init -srepo::init::at_filesystem_root) -ADD_TEST(online "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline) -ADD_TEST(gitdaemon "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::push) -ADD_TEST(ssh "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths) -ADD_TEST(proxy "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::clone::proxy_credentials_in_url -sonline::clone::proxy_credentials_request) +FUNCTION(ADD_CLAR_TEST name) + IF (NOT USE_LEAK_CHECKER STREQUAL "OFF") + ADD_TEST(${name} "${libgit2_SOURCE_DIR}/script/${USE_LEAK_CHECKER}.sh" "${libgit2_BINARY_DIR}/libgit2_clar" ${ARGN}) + ELSE() + ADD_TEST(${name} "${libgit2_BINARY_DIR}/libgit2_clar" ${ARGN}) + ENDIF() +ENDFUNCTION(ADD_CLAR_TEST) + +ADD_CLAR_TEST(offline -v -xonline) +ADD_CLAR_TEST(invasive -v -score::ftruncate -sfilter::stream::bigfile -sodb::largefiles -siterator::workdir::filesystem_gunk -srepo::init -srepo::init::at_filesystem_root) +ADD_CLAR_TEST(online -v -sonline) +ADD_CLAR_TEST(gitdaemon -v -sonline::push) +ADD_CLAR_TEST(ssh -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths -sonline::clone::path_whitespace_ssh) +ADD_CLAR_TEST(proxy -v -sonline::clone::proxy) +ADD_CLAR_TEST(auth_clone -v -sonline::clone::cred) +ADD_CLAR_TEST(auth_clone_and_push -v -sonline::clone::push -sonline::push) diff --git a/tests/README.md b/tests/README.md index 3aeaaf464..b1d70d3bc 100644 --- a/tests/README.md +++ b/tests/README.md @@ -20,3 +20,29 @@ https://github.com/vmg/clar * Make sure everything is fine. * Send your pull request. That's it. + + +Memory leak checks +------------------ + +These are automatically run as part of CI, but if you want to check locally: + +#### Linux + +Uses [`valgrind`](http://www.valgrind.org/): + +```console +$ cmake -DBUILD_CLAR=ON -DVALGRIND=ON .. +$ cmake --build . +$ valgrind --leak-check=full --show-reachable=yes --num-callers=50 --suppressions=../libgit2_clar.supp \ + ./libgit2_clar +``` + +#### macOS + +Uses [`leaks`](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/FindingLeaks.html), which requires XCode installed: + +```console +$ MallocStackLogging=1 MallocScribble=1 MallocLogFile=/dev/null CLAR_AT_EXIT="leaks -quiet \$PPID" \ + ./libgit2_clar +``` diff --git a/tests/apply/apply_helpers.h b/tests/apply/apply_helpers.h index 364daf49d..2d0019abf 100644 --- a/tests/apply/apply_helpers.h +++ b/tests/apply/apply_helpers.h @@ -21,6 +21,27 @@ "-longer. take out the slices of ham, and skim off the grease if any\n" \ "+longer; take out the slices of ham, and skim off the grease if any\n" +/* This is the binary equivalent of DIFF_MODIFY_TWO_FILES */ +#define DIFF_MODIFY_TWO_FILES_BINARY \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "index f51658077d85f2264fa179b4d0848268cb3475c3..ffb36e513f5fdf8a6ba850a20142676a2ac4807d 100644\n" \ + "GIT binary patch\n" \ + "delta 24\n" \ + "fcmX@ja+-zTF*v|6$k9DCSRvRyG(c}7zYP-rT_OhP\n" \ + "\n" \ + "delta 24\n" \ + "fcmX@ja+-zTF*v|6$k9DCSRvRyG(d49zYP-rT;T@W\n" \ + "\n" \ + "diff --git a/veal.txt b/veal.txt\n" \ + "index 94d2c01087f48213bd157222d54edfefd77c9bba..a7b066537e6be7109abfe4ff97b675d4e077da20 100644\n" \ + "GIT binary patch\n" \ + "delta 26\n" \ + "hcmX@kah!uI%+=9HA=p1OKyM?L03)OIW@$zpW&mXg25bNT\n" \ + "\n" \ + "delta 26\n" \ + "hcmX@kah!uI%+=9HA=p1OKyf3N03)N`W@$zpW&mU#22ub3\n" \ + "\n" + #define DIFF_DELETE_FILE \ "diff --git a/gravy.txt b/gravy.txt\n" \ "deleted file mode 100644\n" \ diff --git a/tests/apply/check.c b/tests/apply/check.c new file mode 100644 index 000000000..9e42365ed --- /dev/null +++ b/tests/apply/check.c @@ -0,0 +1,121 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_apply_check__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_apply_check__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_check__generate_diff(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + cl_git_pass(git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707")); + cl_git_pass(git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f")); + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &diff_opts)); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, &opts)); + + validate_index_unchanged(repo); + validate_workdir_unchanged(repo); + + git_diff_free(diff); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + +void test_apply_check__parsed_diff(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); + + validate_index_unchanged(repo); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_check__binary(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES_BINARY, + strlen(DIFF_MODIFY_TWO_FILES_BINARY))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); + + validate_index_unchanged(repo); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_check__does_not_apply(void) +{ + git_diff *diff; + git_index *index; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_remove(index, "veal.txt", 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + + git_diff_free(diff); +} diff --git a/tests/apply/fromdiff.c b/tests/apply/fromdiff.c index 8a6d8fa0a..e9329f6d3 100644 --- a/tests/apply/fromdiff.c +++ b/tests/apply/fromdiff.c @@ -131,6 +131,17 @@ void test_apply_fromdiff__lastline(void) PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL)); } +void test_apply_fromdiff__change_middle_and_lastline_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_MIDDLE_AND_LASTLINE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_AND_LASTLINE_NOCONTEXT, &diff_opts)); +} + void test_apply_fromdiff__prepend(void) { cl_git_pass(apply_buf( @@ -333,3 +344,36 @@ void test_apply_fromdiff__binary_delete(void) NULL, NULL, NULL, &binary_opts)); } + +void test_apply_fromdiff__patching_correctly_truncates_source(void) +{ + git_buf original = GIT_BUF_INIT, patched = GIT_BUF_INIT; + git_patch *patch; + unsigned int mode; + char *path; + + cl_git_pass(git_patch_from_buffers(&patch, + "foo\nbar", 7, "file.txt", + "foo\nfoo", 7, "file.txt", NULL)); + + /* + * Previously, we would fail to correctly truncate the source buffer if + * the source has more than one line and ends with a non-newline + * character. In the following call, we thus truncate the source string + * in the middle of the second line. Without the bug fixed, we would + * successfully apply the patch to the source and return success. With + * the overflow being fixed, we should return an error. + */ + cl_git_fail_with(GIT_EAPPLYFAIL, + git_apply__patch(&patched, &path, &mode, + "foo\nbar\n", 5, patch, NULL)); + + /* Verify that the patch succeeds if we do not truncate */ + cl_git_pass(git_apply__patch(&patched, &path, &mode, + "foo\nbar\n", 7, patch, NULL)); + + git_buf_dispose(&original); + git_buf_dispose(&patched); + git_patch_free(patch); + git__free(path); +} diff --git a/tests/apply/tree.c b/tests/apply/tree.c index f35b13ce0..5154f134f 100644 --- a/tests/apply/tree.c +++ b/tests/apply/tree.c @@ -1,4 +1,5 @@ #include "clar_libgit2.h" +#include "apply_helpers.h" #include "../merge/merge_helpers.h" static git_repository *repo; @@ -56,3 +57,38 @@ void test_apply_tree__one(void) git_commit_free(b_commit); } +void test_apply_tree__adds_file(void) +{ + git_oid a_oid; + git_commit *a_commit; + git_tree *a_tree; + git_diff *diff; + git_index *index = NULL; + + struct merge_index_entry expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); + + cl_git_pass(git_apply_to_tree(&index, repo, a_tree, diff, NULL)); + merge_test_index(index, expected, 7); + + git_index_free(index); + git_diff_free(diff); + git_tree_free(a_tree); + git_commit_free(a_commit); +} diff --git a/tests/attr/attr_expect.h b/tests/attr/attr_expect.h index 70f1ab4f5..faf7a1181 100644 --- a/tests/attr/attr_expect.h +++ b/tests/attr/attr_expect.h @@ -23,15 +23,15 @@ GIT_INLINE(void) attr_check_expected( { switch (expected) { case EXPECT_TRUE: - cl_assert_(GIT_ATTR_TRUE(value), name); + cl_assert_(GIT_ATTR_IS_TRUE(value), name); break; case EXPECT_FALSE: - cl_assert_(GIT_ATTR_FALSE(value), name); + cl_assert_(GIT_ATTR_IS_FALSE(value), name); break; case EXPECT_UNDEFINED: - cl_assert_(GIT_ATTR_UNSPECIFIED(value), name); + cl_assert_(GIT_ATTR_IS_UNSPECIFIED(value), name); break; case EXPECT_STRING: diff --git a/tests/attr/file.c b/tests/attr/file.c index 1f4108c3c..1ac48c0fe 100644 --- a/tests/attr/file.c +++ b/tests/attr/file.c @@ -26,7 +26,7 @@ void test_attr_file__simple_read(void) assign = get_assign(rule, 0); cl_assert(assign != NULL); cl_assert_equal_s("binary", assign->name); - cl_assert(GIT_ATTR_TRUE(assign->value)); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); git_attr_file__free(file); } @@ -53,7 +53,7 @@ void test_attr_file__match_variants(void) assign = get_assign(rule,0); cl_assert_equal_s("attr0", assign->name); cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); - cl_assert(GIT_ATTR_TRUE(assign->value)); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); rule = get_rule(1); cl_assert_equal_s("pat1", rule->match.pattern); @@ -83,7 +83,7 @@ void test_attr_file__match_variants(void) cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0); assign = get_assign(rule,0); cl_assert_equal_s("attr7", assign->name); - cl_assert(GIT_ATTR_TRUE(assign->value)); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); rule = get_rule(8); cl_assert_equal_s("pat8 with spaces", rule->match.pattern); @@ -144,11 +144,11 @@ void test_attr_file__assign_variants(void) assign = git_attr_rule__lookup_assignment(rule, "multiple"); cl_assert(assign); cl_assert_equal_s("multiple", assign->name); - cl_assert(GIT_ATTR_TRUE(assign->value)); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); assign = git_attr_rule__lookup_assignment(rule, "single"); cl_assert(assign); cl_assert_equal_s("single", assign->name); - cl_assert(GIT_ATTR_FALSE(assign->value)); + cl_assert(GIT_ATTR_IS_FALSE(assign->value)); assign = git_attr_rule__lookup_assignment(rule, "values"); cl_assert(assign); cl_assert_equal_s("values", assign->name); @@ -170,7 +170,7 @@ void test_attr_file__assign_variants(void) assign = git_attr_rule__lookup_assignment(rule, "again"); cl_assert(assign); cl_assert_equal_s("again", assign->name); - cl_assert(GIT_ATTR_TRUE(assign->value)); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); assign = git_attr_rule__lookup_assignment(rule, "another"); cl_assert(assign); cl_assert_equal_s("another", assign->name); @@ -181,16 +181,11 @@ void test_attr_file__assign_variants(void) git_attr_file__free(file); } -void test_attr_file__check_attr_examples(void) +static void assert_examples(git_attr_file *file) { - git_attr_file *file; git_attr_rule *rule; git_attr_assignment *assign; - cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3"))); - cl_assert_equal_s(cl_fixture("attr/attr3"), file->entry->path); - cl_assert(file->rules.length == 3); - rule = get_rule(0); cl_assert_equal_s("*.java", rule->match.pattern); cl_assert(rule->assigns.length == 3); @@ -199,10 +194,10 @@ void test_attr_file__check_attr_examples(void) cl_assert_equal_s("java", assign->value); assign = git_attr_rule__lookup_assignment(rule, "crlf"); cl_assert_equal_s("crlf", assign->name); - cl_assert(GIT_ATTR_FALSE(assign->value)); + cl_assert(GIT_ATTR_IS_FALSE(assign->value)); assign = git_attr_rule__lookup_assignment(rule, "myAttr"); cl_assert_equal_s("myAttr", assign->name); - cl_assert(GIT_ATTR_TRUE(assign->value)); + cl_assert(GIT_ATTR_IS_TRUE(assign->value)); assign = git_attr_rule__lookup_assignment(rule, "missing"); cl_assert(assign == NULL); @@ -211,7 +206,7 @@ void test_attr_file__check_attr_examples(void) cl_assert(rule->assigns.length == 1); assign = get_assign(rule, 0); cl_assert_equal_s("myAttr", assign->name); - cl_assert(GIT_ATTR_UNSPECIFIED(assign->value)); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(assign->value)); rule = get_rule(2); cl_assert_equal_s("README", rule->match.pattern); @@ -219,6 +214,30 @@ void test_attr_file__check_attr_examples(void) assign = get_assign(rule, 0); cl_assert_equal_s("caveat", assign->name); cl_assert_equal_s("unspecified", assign->value); +} + +void test_attr_file__check_attr_examples(void) +{ + git_attr_file *file; + + cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3"))); + cl_assert_equal_s(cl_fixture("attr/attr3"), file->entry->path); + cl_assert(file->rules.length == 3); + + assert_examples(file); + + git_attr_file__free(file); +} + +void test_attr_file__whitespace(void) +{ + git_attr_file *file; + + cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr4"))); + cl_assert_equal_s(cl_fixture("attr/attr4"), file->entry->path); + cl_assert(file->rules.length == 3); + + assert_examples(file); git_attr_file__free(file); } diff --git a/tests/attr/flags.c b/tests/attr/flags.c index 80c6e1171..6470ec732 100644 --- a/tests/attr/flags.c +++ b/tests/attr/flags.c @@ -15,7 +15,7 @@ void test_attr_flags__bare(void) cl_git_pass(git_attr_get( &value, repo, GIT_ATTR_CHECK_NO_SYSTEM, "README.md", "diff")); - cl_assert(GIT_ATTR_UNSPECIFIED(value)); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(value)); } void test_attr_flags__index_vs_workdir(void) @@ -29,7 +29,7 @@ void test_attr_flags__index_vs_workdir(void) cl_git_pass(git_attr_get( &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, "README.md", "bar")); - cl_assert(GIT_ATTR_FALSE(value)); + cl_assert(GIT_ATTR_IS_FALSE(value)); cl_git_pass(git_attr_get( &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, @@ -39,13 +39,13 @@ void test_attr_flags__index_vs_workdir(void) cl_git_pass(git_attr_get( &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, "README.txt", "foo")); - cl_assert(GIT_ATTR_FALSE(value)); + cl_assert(GIT_ATTR_IS_FALSE(value)); /* index then wd */ cl_git_pass(git_attr_get( &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, "README.md", "bar")); - cl_assert(GIT_ATTR_TRUE(value)); + cl_assert(GIT_ATTR_IS_TRUE(value)); cl_git_pass(git_attr_get( &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, @@ -55,7 +55,7 @@ void test_attr_flags__index_vs_workdir(void) cl_git_pass(git_attr_get( &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, "README.txt", "foo")); - cl_assert(GIT_ATTR_TRUE(value)); + cl_assert(GIT_ATTR_IS_TRUE(value)); } void test_attr_flags__subdir(void) @@ -77,7 +77,7 @@ void test_attr_flags__subdir(void) cl_git_pass(git_attr_get( &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, "sub/sub/README.txt", "again")); - cl_assert(GIT_ATTR_TRUE(value)); + cl_assert(GIT_ATTR_IS_TRUE(value)); cl_git_pass(git_attr_get( &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX, @@ -98,7 +98,7 @@ void test_attr_flags__subdir(void) cl_git_pass(git_attr_get( &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, "sub/sub/README.txt", "again")); - cl_assert(GIT_ATTR_TRUE(value)); + cl_assert(GIT_ATTR_IS_TRUE(value)); cl_git_pass(git_attr_get( &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE, diff --git a/tests/attr/lookup.c b/tests/attr/lookup.c index 71e87cbae..6063468cb 100644 --- a/tests/attr/lookup.c +++ b/tests/attr/lookup.c @@ -19,7 +19,7 @@ void test_attr_lookup__simple(void) cl_assert(!path.is_dir); cl_git_pass(git_attr_file__lookup_one(file,&path,"binary",&value)); - cl_assert(GIT_ATTR_TRUE(value)); + cl_assert(GIT_ATTR_IS_TRUE(value)); cl_git_pass(git_attr_file__lookup_one(file,&path,"missing",&value)); cl_assert(!value); @@ -252,7 +252,7 @@ void test_attr_lookup__from_buffer(void) cl_git_pass(git_attr_file__new(&file, NULL, 0)); - cl_git_pass(git_attr_file__parse_buffer(NULL, file, "a* foo\nabc bar\n* baz")); + cl_git_pass(git_attr_file__parse_buffer(NULL, file, "a* foo\nabc bar\n* baz", true)); cl_assert(file->rules.length == 3); diff --git a/tests/attr/macro.c b/tests/attr/macro.c new file mode 100644 index 000000000..1fbfd137f --- /dev/null +++ b/tests/attr/macro.c @@ -0,0 +1,197 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "clar_libgit2.h" + +#include "git2/sys/repository.h" +#include "attr.h" + +static git_repository *g_repo = NULL; + +void test_attr_macro__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +void test_attr_macro__macros(void) +{ + const char *names[7] = { "rootattr", "binary", "diff", "crlf", "merge", "text", "frotz" }; + const char *names2[5] = { "mymacro", "positive", "negative", "rootattr", "another" }; + const char *names3[3] = { "macro2", "multi2", "multi3" }; + const char *values[7]; + + g_repo = cl_git_sandbox_init("attr"); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "binfile", 7, names)); + + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[1])); + cl_assert(GIT_ATTR_IS_FALSE(values[2])); + cl_assert(GIT_ATTR_IS_FALSE(values[3])); + cl_assert(GIT_ATTR_IS_FALSE(values[4])); + cl_assert(GIT_ATTR_IS_FALSE(values[5])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[6])); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 5, names2)); + + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[1])); + cl_assert(GIT_ATTR_IS_FALSE(values[2])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); + cl_assert_equal_s("77", values[4]); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 3, names3)); + + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_FALSE(values[1])); + cl_assert_equal_s("answer", values[2]); +} + +void test_attr_macro__bad_macros(void) +{ + const char *names[6] = { "rootattr", "positive", "negative", + "firstmacro", "secondmacro", "thirdmacro" }; + const char *values[6]; + + g_repo = cl_git_sandbox_init("attr"); + + cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_bad", 6, names)); + + /* these three just confirm that the "mymacro" rule ran */ + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[1])); + cl_assert(GIT_ATTR_IS_FALSE(values[2])); + + /* file contains: + * # let's try some malicious macro defs + * [attr]firstmacro -thirdmacro -secondmacro + * [attr]secondmacro firstmacro -firstmacro + * [attr]thirdmacro secondmacro=hahaha -firstmacro + * macro_bad firstmacro secondmacro thirdmacro + * + * firstmacro assignment list ends up with: + * -thirdmacro -secondmacro + * secondmacro assignment list expands "firstmacro" and ends up with: + * -thirdmacro -secondmacro -firstmacro + * thirdmacro assignment don't expand so list ends up with: + * secondmacro="hahaha" + * + * macro_bad assignment list ends up with: + * -thirdmacro -secondmacro firstmacro && + * -thirdmacro -secondmacro -firstmacro secondmacro && + * secondmacro="hahaha" thirdmacro + * + * so summary results should be: + * -firstmacro secondmacro="hahaha" thirdmacro + */ + cl_assert(GIT_ATTR_IS_FALSE(values[3])); + cl_assert_equal_s("hahaha", values[4]); + cl_assert(GIT_ATTR_IS_TRUE(values[5])); +} + +void test_attr_macro__macros_in_root_wd_apply(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(p_mkdir("empty_standard_repo/dir", 0777)); + cl_git_rewritefile("empty_standard_repo/.gitattributes", "[attr]customattr key=value\n"); + cl_git_rewritefile("empty_standard_repo/dir/.gitattributes", "file customattr\n"); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "dir/file", "key")); + cl_assert_equal_s(value, "value"); +} + +void test_attr_macro__changing_macro_in_root_wd_updates_attributes(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_rewritefile("empty_standard_repo/.gitattributes", + "[attr]customattr key=first\n" + "file customattr\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "key")); + cl_assert_equal_s(value, "first"); + + cl_git_rewritefile("empty_standard_repo/.gitattributes", + "[attr]customattr key=second\n" + "file customattr\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "key")); + cl_assert_equal_s(value, "second"); +} + +void test_attr_macro__macros_in_subdir_do_not_apply(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(p_mkdir("empty_standard_repo/dir", 0777)); + cl_git_rewritefile("empty_standard_repo/dir/.gitattributes", + "[attr]customattr key=value\n" + "file customattr\n"); + + /* This should _not_ pass, as macros in subdirectories shall be ignored */ + cl_git_pass(git_attr_get(&value, g_repo, 0, "dir/file", "key")); + cl_assert_equal_p(value, NULL); +} + +void test_attr_macro__adding_macro_succeeds(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_attr_add_macro(g_repo, "macro", "key=value")); + cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro\n"); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "key")); + cl_assert_equal_s(value, "value"); +} + +void test_attr_macro__adding_boolean_macros_succeeds(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_attr_add_macro(g_repo, "macro-pos", "positive")); + cl_git_pass(git_attr_add_macro(g_repo, "macro-neg", "-negative")); + cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro-pos macro-neg\n"); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "positive")); + cl_assert(GIT_ATTR_IS_TRUE(value)); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "negative")); + cl_assert(GIT_ATTR_IS_FALSE(value)); +} + +void test_attr_macro__redefining_macro_succeeds(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_attr_add_macro(g_repo, "macro", "key=value1")); + cl_git_pass(git_attr_add_macro(g_repo, "macro", "key=value2")); + cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro"); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "key")); + cl_assert_equal_s(value, "value2"); +} + +void test_attr_macro__recursive_macro_resolves(void) +{ + const char *value; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_attr_add_macro(g_repo, "expandme", "key=value")); + cl_git_pass(git_attr_add_macro(g_repo, "macro", "expandme")); + cl_git_rewritefile("empty_standard_repo/.gitattributes", "file.txt macro"); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "key")); + cl_assert_equal_s(value, "value"); +} diff --git a/tests/attr/repo.c b/tests/attr/repo.c index eb4300b4e..36beeb095 100644 --- a/tests/attr/repo.c +++ b/tests/attr/repo.c @@ -1,7 +1,8 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "git2/attr.h" #include "attr.h" +#include "sysdir.h" #include "attr_expect.h" #include "git2/sys/repository.h" @@ -17,6 +18,7 @@ void test_attr_repo__cleanup(void) { cl_git_sandbox_cleanup(); g_repo = NULL; + cl_sandbox_set_search_path_defaults(); } static struct attr_expected get_one_test_cases[] = { @@ -104,23 +106,23 @@ void test_attr_repo__get_many(void) cl_git_pass(git_attr_get_many(values, g_repo, 0, "root_test1", 4, names)); - cl_assert(GIT_ATTR_TRUE(values[0])); - cl_assert(GIT_ATTR_TRUE(values[1])); - cl_assert(GIT_ATTR_UNSPECIFIED(values[2])); - cl_assert(GIT_ATTR_UNSPECIFIED(values[3])); + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[1])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); cl_git_pass(git_attr_get_many(values, g_repo, 0, "root_test2", 4, names)); - cl_assert(GIT_ATTR_TRUE(values[0])); - cl_assert(GIT_ATTR_FALSE(values[1])); - cl_assert(GIT_ATTR_UNSPECIFIED(values[2])); - cl_assert(GIT_ATTR_UNSPECIFIED(values[3])); + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_FALSE(values[1])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); cl_git_pass(git_attr_get_many(values, g_repo, 0, "sub/subdir_test1", 4, names)); - cl_assert(GIT_ATTR_TRUE(values[0])); - cl_assert(GIT_ATTR_TRUE(values[1])); - cl_assert(GIT_ATTR_UNSPECIFIED(values[2])); + cl_assert(GIT_ATTR_IS_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[1])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); cl_assert_equal_s("yes", values[3]); } @@ -134,9 +136,9 @@ void test_attr_repo__get_many_in_place(void) cl_git_pass(git_attr_get_many(vals, g_repo, 0, "sub/subdir_test1", 4, vals)); - cl_assert(GIT_ATTR_TRUE(vals[0])); - cl_assert(GIT_ATTR_TRUE(vals[1])); - cl_assert(GIT_ATTR_UNSPECIFIED(vals[2])); + cl_assert(GIT_ATTR_IS_TRUE(vals[0])); + cl_assert(GIT_ATTR_IS_TRUE(vals[1])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(vals[2])); cl_assert_equal_s("yes", vals[3]); } @@ -202,89 +204,19 @@ void test_attr_repo__manpage_example(void) const char *value; cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "foo")); - cl_assert(GIT_ATTR_TRUE(value)); + cl_assert(GIT_ATTR_IS_TRUE(value)); cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "bar")); - cl_assert(GIT_ATTR_UNSPECIFIED(value)); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(value)); cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "baz")); - cl_assert(GIT_ATTR_FALSE(value)); + cl_assert(GIT_ATTR_IS_FALSE(value)); cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "merge")); cl_assert_equal_s("filfre", value); cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "frotz")); - cl_assert(GIT_ATTR_UNSPECIFIED(value)); -} - -void test_attr_repo__macros(void) -{ - const char *names[5] = { "rootattr", "binary", "diff", "crlf", "frotz" }; - const char *names2[5] = { "mymacro", "positive", "negative", "rootattr", "another" }; - const char *names3[3] = { "macro2", "multi2", "multi3" }; - const char *values[5]; - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "binfile", 5, names)); - - cl_assert(GIT_ATTR_TRUE(values[0])); - cl_assert(GIT_ATTR_TRUE(values[1])); - cl_assert(GIT_ATTR_FALSE(values[2])); - cl_assert(GIT_ATTR_FALSE(values[3])); - cl_assert(GIT_ATTR_UNSPECIFIED(values[4])); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 5, names2)); - - cl_assert(GIT_ATTR_TRUE(values[0])); - cl_assert(GIT_ATTR_TRUE(values[1])); - cl_assert(GIT_ATTR_FALSE(values[2])); - cl_assert(GIT_ATTR_UNSPECIFIED(values[3])); - cl_assert_equal_s("77", values[4]); - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 3, names3)); - - cl_assert(GIT_ATTR_TRUE(values[0])); - cl_assert(GIT_ATTR_FALSE(values[1])); - cl_assert_equal_s("answer", values[2]); -} - -void test_attr_repo__bad_macros(void) -{ - const char *names[6] = { "rootattr", "positive", "negative", - "firstmacro", "secondmacro", "thirdmacro" }; - const char *values[6]; - - cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_bad", 6, names)); - - /* these three just confirm that the "mymacro" rule ran */ - cl_assert(GIT_ATTR_UNSPECIFIED(values[0])); - cl_assert(GIT_ATTR_TRUE(values[1])); - cl_assert(GIT_ATTR_FALSE(values[2])); - - /* file contains: - * # let's try some malicious macro defs - * [attr]firstmacro -thirdmacro -secondmacro - * [attr]secondmacro firstmacro -firstmacro - * [attr]thirdmacro secondmacro=hahaha -firstmacro - * macro_bad firstmacro secondmacro thirdmacro - * - * firstmacro assignment list ends up with: - * -thirdmacro -secondmacro - * secondmacro assignment list expands "firstmacro" and ends up with: - * -thirdmacro -secondmacro -firstmacro - * thirdmacro assignment don't expand so list ends up with: - * secondmacro="hahaha" - * - * macro_bad assignment list ends up with: - * -thirdmacro -secondmacro firstmacro && - * -thirdmacro -secondmacro -firstmacro secondmacro && - * secondmacro="hahaha" thirdmacro - * - * so summary results should be: - * -firstmacro secondmacro="hahaha" thirdmacro - */ - cl_assert(GIT_ATTR_FALSE(values[3])); - cl_assert_equal_s("hahaha", values[4]); - cl_assert(GIT_ATTR_TRUE(values[5])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(value)); } #define CONTENT "I'm going to be dynamically processed\r\n" \ @@ -357,22 +289,117 @@ void test_attr_repo__bare_repo_with_index(void) cl_git_pass(git_attr_get_many(values, g_repo, 0, "file.txt", 4, names)); - cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[0])); cl_assert_equal_s("foobar", values[1]); - cl_assert(GIT_ATTR_FALSE(values[2])); - cl_assert(GIT_ATTR_UNSPECIFIED(values[3])); + cl_assert(GIT_ATTR_IS_FALSE(values[2])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); cl_git_pass(git_attr_get_many(values, g_repo, 0, "trial.txt", 4, names)); - cl_assert(GIT_ATTR_FALSE(values[0])); + cl_assert(GIT_ATTR_IS_FALSE(values[0])); cl_assert_equal_s("barfoo", values[1]); - cl_assert(GIT_ATTR_UNSPECIFIED(values[2])); - cl_assert(GIT_ATTR_TRUE(values[3])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[2])); + cl_assert(GIT_ATTR_IS_TRUE(values[3])); cl_git_pass(git_attr_get_many(values, g_repo, 0, "sub/sub/subdir.txt", 4, names)); - cl_assert(GIT_ATTR_TRUE(values[0])); + cl_assert(GIT_ATTR_IS_TRUE(values[0])); cl_assert_equal_s("foobar", values[1]); - cl_assert(GIT_ATTR_FALSE(values[2])); - cl_assert(GIT_ATTR_UNSPECIFIED(values[3])); + cl_assert(GIT_ATTR_IS_FALSE(values[2])); + cl_assert(GIT_ATTR_IS_UNSPECIFIED(values[3])); +} + +void test_attr_repo__sysdir(void) +{ + git_buf sysdir = GIT_BUF_INIT; + const char *value; + + cl_git_pass(p_mkdir("system", 0777)); + cl_git_rewritefile("system/gitattributes", "file merge=foo"); + cl_git_pass(git_buf_joinpath(&sysdir, clar_sandbox_path(), "system")); + cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, sysdir.ptr)); + g_repo = cl_git_sandbox_reopen(); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "merge")); + cl_assert_equal_s(value, "foo"); + + cl_git_pass(p_unlink("system/gitattributes")); + cl_git_pass(p_rmdir("system")); + git_buf_dispose(&sysdir); +} + +void test_attr_repo__sysdir_with_session(void) +{ + const char *values[2], *attrs[2] = { "foo", "bar" }; + git_buf sysdir = GIT_BUF_INIT; + git_attr_session session; + + cl_git_pass(p_mkdir("system", 0777)); + cl_git_rewritefile("system/gitattributes", "file foo=1 bar=2"); + cl_git_pass(git_buf_joinpath(&sysdir, clar_sandbox_path(), "system")); + cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, sysdir.ptr)); + g_repo = cl_git_sandbox_reopen(); + + cl_git_pass(git_attr_session__init(&session, g_repo)); + cl_git_pass(git_attr_get_many_with_session(values, g_repo, &session, 0, "file", ARRAY_SIZE(attrs), attrs)); + + cl_assert_equal_s(values[0], "1"); + cl_assert_equal_s(values[1], "2"); + + cl_git_pass(p_unlink("system/gitattributes")); + cl_git_pass(p_rmdir("system")); + git_buf_dispose(&sysdir); + git_attr_session__free(&session); +} + +void test_attr_repo__rewrite(void) +{ + const char *value; + + cl_git_rewritefile("attr/.gitattributes", "file.txt foo=first\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); + cl_assert_equal_s(value, "first"); + + cl_git_rewritefile("attr/.gitattributes", "file.txt foo=second\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); + cl_assert_equal_s(value, "second"); + + cl_git_rewritefile("attr/.gitattributes", "file.txt other=value\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); + cl_assert_equal_p(value, NULL); +} + +void test_attr_repo__rewrite_sysdir(void) +{ + git_buf sysdir = GIT_BUF_INIT; + const char *value; + + cl_git_pass(p_mkdir("system", 0777)); + cl_git_pass(git_buf_joinpath(&sysdir, clar_sandbox_path(), "system")); + cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, sysdir.ptr)); + g_repo = cl_git_sandbox_reopen(); + + cl_git_rewritefile("system/gitattributes", "file foo=first"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "foo")); + cl_assert_equal_s(value, "first"); + + cl_git_rewritefile("system/gitattributes", "file foo=second"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file", "foo")); + cl_assert_equal_s(value, "second"); + + git_buf_dispose(&sysdir); +} + +void test_attr_repo__unlink(void) +{ + const char *value; + + cl_git_rewritefile("attr/.gitattributes", "file.txt foo=value1\n"); + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); + cl_assert_equal_s(value, "value1"); + + cl_git_pass(p_unlink("attr/.gitattributes")); + + cl_git_pass(git_attr_get(&value, g_repo, 0, "file.txt", "foo")); + cl_assert_equal_p(value, NULL); } diff --git a/tests/blame/blame_helpers.c b/tests/blame/blame_helpers.c index 61e87350c..6b3ce677d 100644 --- a/tests/blame/blame_helpers.c +++ b/tests/blame/blame_helpers.c @@ -31,13 +31,13 @@ void check_blame_hunk_index(git_repository *repo, git_blame *blame, int idx, } if (hunk->final_start_line_number != start_line) { - hunk_message(idx, hunk, "mismatched start line number: expected %d, got %d", + hunk_message(idx, hunk, "mismatched start line number: expected %"PRIuZ", got %"PRIuZ, start_line, hunk->final_start_line_number); } cl_assert_equal_i(hunk->final_start_line_number, start_line); if (hunk->lines_in_hunk != len) { - hunk_message(idx, hunk, "mismatched line count: expected %d, got %d", + hunk_message(idx, hunk, "mismatched line count: expected %"PRIuZ", got %"PRIuZ, len, hunk->lines_in_hunk); } cl_assert_equal_i(hunk->lines_in_hunk, len); diff --git a/tests/blame/blame_helpers.h b/tests/blame/blame_helpers.h index fd5a35d2c..5b34b4aef 100644 --- a/tests/blame/blame_helpers.h +++ b/tests/blame/blame_helpers.h @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "blame.h" -void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...); +void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...) GIT_FORMAT_PRINTF(3, 4); void check_blame_hunk_index( git_repository *repo, diff --git a/tests/blame/buffer.c b/tests/blame/buffer.c index 340b1dced..06d5042dd 100644 --- a/tests/blame/buffer.c +++ b/tests/blame/buffer.c @@ -17,6 +17,32 @@ void test_blame_buffer__cleanup(void) git_repository_free(g_repo); } +void test_blame_buffer__index(void) +{ + const git_blame_hunk *hunk; + const char *buffer = "Hello\nWorld!"; + + /* + * We need to open a different file from the ones used in other tests. Close + * the one opened in test_blame_buffer__initialize() to avoid a leak. + */ + git_blame_free(g_fileblame); + g_fileblame = NULL; + cl_git_pass(git_blame_file(&g_fileblame, g_repo, "file.txt", NULL)); + + cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer))); + cl_assert_equal_i(2, git_blame_get_hunk_count(g_bufferblame)); + + check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 1, 0, "836bc00b", "file.txt"); + hunk = git_blame_get_hunk_byline(g_bufferblame, 1); + cl_assert(hunk); + cl_assert_equal_s("lhchavez", hunk->final_signature->name); + check_blame_hunk_index(g_repo, g_bufferblame, 1, 2, 1, 0, "00000000", "file.txt"); + hunk = git_blame_get_hunk_byline(g_bufferblame, 2); + cl_assert(hunk); + cl_assert(hunk->final_signature == NULL); +} + void test_blame_buffer__added_line(void) { const git_blame_hunk *hunk; diff --git a/tests/blame/simple.c b/tests/blame/simple.c index 30b78168f..16e7bc400 100644 --- a/tests/blame/simple.c +++ b/tests/blame/simple.c @@ -203,6 +203,14 @@ void test_blame_simple__trivial_libgit2(void) check_blame_hunk_index(g_repo, g_blame, 49, 60, 1, 0, "d12299fe", "src/git.h"); } +/* This was leading to segfaults on some systems during cache eviction. */ +void test_blame_simple__trivial_libgit2_under_cache_pressure(void) +{ + ssize_t old_max_storage = git_cache__max_storage; + git_cache__max_storage = 1024 * 1024; + test_blame_simple__trivial_libgit2(); + git_cache__max_storage = old_max_storage; +} /* * $ git blame -n b.txt -L 8 diff --git a/tests/checkout/binaryunicode.c b/tests/checkout/binaryunicode.c index 5ba79213a..edb5cfaf5 100644 --- a/tests/checkout/binaryunicode.c +++ b/tests/checkout/binaryunicode.c @@ -2,7 +2,7 @@ #include "refs.h" #include "repo/repo_helpers.h" #include "path.h" -#include "fileops.h" +#include "futils.h" static git_repository *g_repo; diff --git a/tests/checkout/checkout_helpers.c b/tests/checkout/checkout_helpers.c index 8256644db..95af5d396 100644 --- a/tests/checkout/checkout_helpers.c +++ b/tests/checkout/checkout_helpers.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "checkout_helpers.h" #include "refs.h" -#include "fileops.h" +#include "futils.h" #include "index.h" void assert_on_branch(git_repository *repo, const char *branch) diff --git a/tests/checkout/conflict.c b/tests/checkout/conflict.c index 914b3c2de..dae3f295e 100644 --- a/tests/checkout/conflict.c +++ b/tests/checkout/conflict.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "git2/repository.h" #include "git2/sys/index.h" -#include "fileops.h" +#include "futils.h" #include "repository.h" static git_repository *g_repo; @@ -192,7 +192,7 @@ static void ensure_workdir_link( { int symlinks; - cl_git_pass(git_repository__cvar(&symlinks, repo, GIT_CVAR_SYMLINKS)); + cl_git_pass(git_repository__configmap_lookup(&symlinks, repo, GIT_CONFIGMAP_SYMLINKS)); if (!symlinks) { ensure_workdir_contents(path, target); diff --git a/tests/checkout/crlf.c b/tests/checkout/crlf.c index ff3a2dc64..65e13a6fd 100644 --- a/tests/checkout/crlf.c +++ b/tests/checkout/crlf.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "checkout_helpers.h" #include "../filter/crlf.h" -#include "fileops.h" +#include "futils.h" #include "git2/checkout.h" #include "repository.h" diff --git a/tests/checkout/head.c b/tests/checkout/head.c index 99061466f..799123086 100644 --- a/tests/checkout/head.c +++ b/tests/checkout/head.c @@ -2,7 +2,7 @@ #include "refs.h" #include "repo/repo_helpers.h" #include "path.h" -#include "fileops.h" +#include "futils.h" static git_repository *g_repo; diff --git a/tests/checkout/icase.c b/tests/checkout/icase.c index e6c640ef5..077d24cd3 100644 --- a/tests/checkout/icase.c +++ b/tests/checkout/icase.c @@ -33,7 +33,7 @@ void test_checkout_icase__initialize(void) cl_git_pass(git_reference_name_to_id(&id, repo, "refs/heads/dir")); cl_git_pass(git_object_lookup(&obj, repo, &id, GIT_OBJECT_ANY)); - git_checkout_init_options(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION); + git_checkout_options_init(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION); checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; } @@ -96,7 +96,7 @@ static int symlink_or_fake(git_repository *repo, const char *a, const char *b) { int symlinks; - cl_git_pass(git_repository__cvar(&symlinks, repo, GIT_CVAR_SYMLINKS)); + cl_git_pass(git_repository__configmap_lookup(&symlinks, repo, GIT_CONFIGMAP_SYMLINKS)); if (symlinks) return p_symlink(a, b); diff --git a/tests/checkout/index.c b/tests/checkout/index.c index 8272c68b3..a76c471e8 100644 --- a/tests/checkout/index.c +++ b/tests/checkout/index.c @@ -2,7 +2,7 @@ #include "checkout_helpers.h" #include "git2/checkout.h" -#include "fileops.h" +#include "futils.h" #include "repository.h" #include "remote.h" #include "repo/repo_helpers.h" @@ -148,13 +148,15 @@ void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) static void populate_symlink_workdir(void) { + git_buf path = GIT_BUF_INIT; git_repository *repo; git_remote *origin; git_object *target; const char *url = git_repository_path(g_repo); - cl_git_pass(git_repository_init(&repo, "../symlink.git", true)); + cl_git_pass(git_buf_joinpath(&path, clar_sandbox_path(), "symlink.git")); + cl_git_pass(git_repository_init(&repo, path.ptr, true)); cl_git_pass(git_repository_set_workdir(repo, "symlink", 1)); /* Delete the `origin` repo (if it exists) so we can recreate it. */ @@ -166,8 +168,10 @@ static void populate_symlink_workdir(void) cl_git_pass(git_revparse_single(&target, repo, "remotes/origin/master")); cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL)); + git_object_free(target); git_repository_free(repo); + git_buf_dispose(&path); } void test_checkout_index__honor_coresymlinks_default_true(void) @@ -177,7 +181,7 @@ void test_checkout_index__honor_coresymlinks_default_true(void) cl_must_pass(p_mkdir("symlink", 0777)); - if (!filesystem_supports_symlinks("symlink/test")) + if (!git_path_supports_symlinks("symlink/test")) cl_skip(); #ifdef GIT_WIN32 @@ -210,7 +214,7 @@ void test_checkout_index__honor_coresymlinks_default_false(void) * supports symlinks. Bail entirely on POSIX platforms that * do support symlinks. */ - if (filesystem_supports_symlinks("symlink/test")) + if (git_path_supports_symlinks("symlink/test")) cl_skip(); #endif @@ -222,7 +226,7 @@ void test_checkout_index__coresymlinks_set_to_true_fails_when_unsupported(void) { git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - if (filesystem_supports_symlinks("testrepo/test")) { + if (git_path_supports_symlinks("testrepo/test")) { cl_skip(); } @@ -238,7 +242,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) char link_data[GIT_PATH_MAX]; size_t link_size = GIT_PATH_MAX; - if (!filesystem_supports_symlinks("testrepo/test")) { + if (!git_path_supports_symlinks("testrepo/test")) { cl_skip(); } diff --git a/tests/checkout/nasty.c b/tests/checkout/nasty.c index 288341a53..611e850d3 100644 --- a/tests/checkout/nasty.c +++ b/tests/checkout/nasty.c @@ -4,7 +4,7 @@ #include "git2/checkout.h" #include "repository.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" static const char *repo_name = "nasty"; static git_repository *repo; diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index 47ded0f7c..380d25101 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -4,7 +4,7 @@ #include "git2/checkout.h" #include "repository.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" static git_repository *g_repo; static git_checkout_options g_opts; diff --git a/tests/checkout/typechange.c b/tests/checkout/typechange.c index 708af5956..db3f02a5c 100644 --- a/tests/checkout/typechange.c +++ b/tests/checkout/typechange.c @@ -3,7 +3,7 @@ #include "git2/checkout.h" #include "path.h" #include "posix.h" -#include "fileops.h" +#include "futils.h" static git_repository *g_repo = NULL; diff --git a/tests/cherrypick/bare.c b/tests/cherrypick/bare.c index 135336507..50e8d8600 100644 --- a/tests/cherrypick/bare.c +++ b/tests/cherrypick/bare.c @@ -2,7 +2,7 @@ #include "clar_libgit2.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" #include "git2/cherrypick.h" #include "../merge/merge_helpers.h" diff --git a/tests/cherrypick/workdir.c b/tests/cherrypick/workdir.c index 8f6644996..10e8c2d8b 100644 --- a/tests/cherrypick/workdir.c +++ b/tests/cherrypick/workdir.c @@ -2,7 +2,7 @@ #include "clar_libgit2.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" #include "git2/cherrypick.h" #include "../merge/merge_helpers.h" diff --git a/tests/clar.c b/tests/clar.c index 27d35e1c7..ead13f46a 100644 --- a/tests/clar.c +++ b/tests/clar.c @@ -67,7 +67,7 @@ # define PRIxZ "Ix" # endif -# ifdef _MSC_VER +# if defined(_MSC_VER) || defined(__MINGW32__) typedef struct stat STAT_T; # else typedef struct _stat STAT_T; @@ -96,7 +96,7 @@ fixture_path(const char *base, const char *fixture_name); struct clar_error { const char *file; - int line_number; + size_t line_number; const char *error_msg; char *description; @@ -145,7 +145,7 @@ static struct { int report_suite_names; int write_summary; - const char *summary_filename; + char *summary_filename; struct clar_summary *summary; struct clar_explicit *explicit; @@ -474,8 +474,8 @@ clar_parse_args(int argc, char **argv) case 'r': _clar.write_summary = 1; - _clar.summary_filename = *(argument + 2) ? (argument + 2) : - "summary.xml"; + free(_clar.summary_filename); + _clar.summary_filename = strdup(*(argument + 2) ? (argument + 2) : "summary.xml"); break; default: @@ -493,6 +493,11 @@ clar_test_init(int argc, char **argv) "" ); + if ((_clar.summary_filename = getenv("CLAR_SUMMARY")) != NULL) { + _clar.write_summary = 1; + _clar.summary_filename = strdup(_clar.summary_filename); + } + if (argc > 1) clar_parse_args(argc, argv); @@ -553,6 +558,8 @@ clar_test_shutdown(void) report_next = report->next; free(report); } + + free(_clar.summary_filename); } int @@ -589,7 +596,7 @@ void clar__skip(void) void clar__fail( const char *file, - int line, + size_t line, const char *error_msg, const char *description, int should_abort) @@ -621,7 +628,7 @@ void clar__fail( void clar__assert( int condition, const char *file, - int line, + size_t line, const char *error_msg, const char *description, int should_abort) @@ -634,7 +641,7 @@ void clar__assert( void clar__assert_equal( const char *file, - int line, + size_t line, const char *err, int should_abort, const char *fmt, diff --git a/tests/clar.h b/tests/clar.h index 2f9f96b3f..20ff4c895 100644 --- a/tests/clar.h +++ b/tests/clar.h @@ -141,7 +141,7 @@ void clar__skip(void); void clar__fail( const char *file, - int line, + size_t line, const char *error, const char *description, int should_abort); @@ -149,14 +149,14 @@ void clar__fail( void clar__assert( int condition, const char *file, - int line, + size_t line, const char *error, const char *description, int should_abort); void clar__assert_equal( const char *file, - int line, + size_t line, const char *err, int should_abort, const char *fmt, diff --git a/tests/clar/print.h b/tests/clar/print.h index 73c4a8953..2e2b6202c 100644 --- a/tests/clar/print.h +++ b/tests/clar/print.h @@ -20,7 +20,7 @@ static void clar_print_error(int num, const struct clar_report *report, const st { printf(" %d) Failure:\n", num); - printf("%s::%s [%s:%d]\n", + printf("%s::%s [%s:%"PRIuZ"]\n", report->suite, report->test, error->file, diff --git a/tests/clar_libgit2.h b/tests/clar_libgit2.h index 7572b25ed..12175c629 100644 --- a/tests/clar_libgit2.h +++ b/tests/clar_libgit2.h @@ -3,8 +3,8 @@ #include "clar.h" #include -#include #include "common.h" +#include "posix.h" /** * Replace for `clar_must_pass` that passes the last library error as the @@ -219,6 +219,12 @@ void cl_fake_home_cleanup(void *); void cl_sandbox_set_search_path_defaults(void); +#ifdef GIT_WIN32 +# define cl_msleep(x) Sleep(x) +#else +# define cl_msleep(x) usleep(1000 * (x)) +#endif + #ifdef GIT_WIN32 bool cl_sandbox_supports_8dot3(void); #endif diff --git a/tests/clar_libgit2_trace.c b/tests/clar_libgit2_trace.c index aaeeb7810..d4d8d2c37 100644 --- a/tests/clar_libgit2_trace.c +++ b/tests/clar_libgit2_trace.c @@ -1,35 +1,49 @@ -#include "clar_libgit2.h" #include "clar_libgit2_trace.h" +#include "clar_libgit2.h" #include "clar_libgit2_timer.h" #include "trace.h" - struct method { const char *name; void (*git_trace_cb)(git_trace_level_t level, const char *msg); void (*close)(void); }; +static const char *message_prefix(git_trace_level_t level) +{ + switch (level) { + case GIT_TRACE_NONE: + return "[NONE]: "; + case GIT_TRACE_FATAL: + return "[FATAL]: "; + case GIT_TRACE_ERROR: + return "[ERROR]: "; + case GIT_TRACE_WARN: + return "[WARN]: "; + case GIT_TRACE_INFO: + return "[INFO]: "; + case GIT_TRACE_DEBUG: + return "[DEBUG]: "; + case GIT_TRACE_TRACE: + return "[TRACE]: "; + default: + return "[?????]: "; + } +} -#if defined(GIT_TRACE) static void _git_trace_cb__printf(git_trace_level_t level, const char *msg) { - /* TODO Use level to print a per-message prefix. */ - GIT_UNUSED(level); - - printf("%s\n", msg); + printf("%s%s\n", message_prefix(level), msg); } #if defined(GIT_WIN32) static void _git_trace_cb__debug(git_trace_level_t level, const char *msg) { - /* TODO Use level to print a per-message prefix. */ - GIT_UNUSED(level); - + OutputDebugString(message_prefix(level)); OutputDebugString(msg); OutputDebugString("\n"); - printf("%s\n", msg); + printf("%s%s\n", message_prefix(level), msg); } #else #define _git_trace_cb__debug _git_trace_cb__printf @@ -55,7 +69,7 @@ static struct method s_methods[] = { static int s_trace_loaded = 0; static int s_trace_level = GIT_TRACE_NONE; static struct method *s_trace_method = NULL; - +static int s_trace_tests = 0; static int set_method(const char *name) { @@ -101,6 +115,7 @@ static void _load_trace_params(void) { char *sz_level; char *sz_method; + char *sz_tests; s_trace_loaded = 1; @@ -117,6 +132,10 @@ static void _load_trace_params(void) sz_method = cl_getenv("CLAR_TRACE_METHOD"); if (set_method(sz_method) < 0) set_method(NULL); + + sz_tests = cl_getenv("CLAR_TRACE_TESTS"); + if (sz_tests != NULL) + s_trace_tests = 1; } #define HR "================================================================" @@ -139,6 +158,9 @@ void _cl_trace_cb__event_handler( { GIT_UNUSED(payload); + if (!s_trace_tests) + return; + switch (ev) { case CL_TRACE__SUITE_BEGIN: git_trace(GIT_TRACE_TRACE, "\n\n%s\n%s: Begin Suite", HR, suite_name); @@ -201,15 +223,11 @@ void _cl_trace_cb__event_handler( } } -#endif /*GIT_TRACE*/ - /** * Setup/Enable git_trace() based upon settings user's environment. - * */ void cl_global_trace_register(void) { -#if defined(GIT_TRACE) if (!s_trace_loaded) _load_trace_params(); @@ -222,7 +240,6 @@ void cl_global_trace_register(void) git_trace_set(s_trace_level, s_trace_method->git_trace_cb); cl_trace_register(_cl_trace_cb__event_handler, NULL); -#endif } /** @@ -234,7 +251,6 @@ void cl_global_trace_register(void) */ void cl_global_trace_disable(void) { -#if defined(GIT_TRACE) cl_trace_register(NULL, NULL); git_trace_set(GIT_TRACE_NONE, NULL); if (s_trace_method && s_trace_method->close) @@ -244,5 +260,4 @@ void cl_global_trace_disable(void) * since we only want to hit the environment variables * once. */ -#endif } diff --git a/tests/clone/local.c b/tests/clone/local.c index 941691814..b90ff3107 100644 --- a/tests/clone/local.c +++ b/tests/clone/local.c @@ -5,7 +5,7 @@ #include "buffer.h" #include "path.h" #include "posix.h" -#include "fileops.h" +#include "futils.h" static int file_url(git_buf *buf, const char *host, const char *path) { diff --git a/tests/clone/nonetwork.c b/tests/clone/nonetwork.c index 8a8337c04..7ca49085c 100644 --- a/tests/clone/nonetwork.c +++ b/tests/clone/nonetwork.c @@ -1,10 +1,9 @@ #include "clar_libgit2.h" #include "git2/clone.h" -#include "git2/sys/commit.h" #include "../submodule/submodule_helpers.h" #include "remote.h" -#include "fileops.h" +#include "futils.h" #include "repository.h" #define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository" @@ -171,7 +170,7 @@ void test_clone_nonetwork__can_checkout_given_branch(void) } static int clone_cancel_fetch_transfer_progress_cb( - const git_transfer_progress *stats, void *data) + const git_indexer_progress *stats, void *data) { GIT_UNUSED(stats); GIT_UNUSED(data); return -54321; @@ -352,56 +351,3 @@ void test_clone_nonetwork__clone_from_empty_sets_upstream(void) git_repository_free(repo); cl_fixture_cleanup("./repowithunborn"); } - -static int just_return_origin(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload) -{ - GIT_UNUSED(url); GIT_UNUSED(payload); - - return git_remote_lookup(out, repo, name); -} - -static int just_return_repo(git_repository **out, const char *path, int bare, void *payload) -{ - git_submodule *sm = payload; - - GIT_UNUSED(path); GIT_UNUSED(bare); - - return git_submodule_open(out, sm); -} - -void test_clone_nonetwork__clone_submodule(void) -{ - git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; - git_index *index; - git_oid tree_id, commit_id; - git_submodule *sm; - git_signature *sig; - git_repository *sm_repo; - - cl_git_pass(git_repository_init(&g_repo, "willaddsubmodule", false)); - - - /* Create the submodule structure, clone into it and finalize */ - cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "testrepo", true)); - - clone_opts.repository_cb = just_return_repo; - clone_opts.repository_cb_payload = sm; - clone_opts.remote_cb = just_return_origin; - clone_opts.remote_cb_payload = sm; - cl_git_pass(git_clone(&sm_repo, cl_fixture("testrepo.git"), "testrepo", &clone_opts)); - cl_git_pass(git_submodule_add_finalize(sm)); - git_repository_free(sm_repo); - git_submodule_free(sm); - - cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_write_tree(&tree_id, index)); - git_index_free(index); - - cl_git_pass(git_signature_now(&sig, "Submoduler", "submoduler@local")); - cl_git_pass(git_commit_create_from_ids(&commit_id, g_repo, "HEAD", sig, sig, NULL, "A submodule\n", - &tree_id, 0, NULL)); - - git_signature_free(sig); - - assert_submodule_exists(g_repo, "testrepo"); -} diff --git a/tests/clone/transport.c b/tests/clone/transport.c index cccaae219..fa4f65357 100644 --- a/tests/clone/transport.c +++ b/tests/clone/transport.c @@ -3,7 +3,7 @@ #include "git2/clone.h" #include "git2/transport.h" #include "git2/sys/transport.h" -#include "fileops.h" +#include "futils.h" static int custom_transport( git_transport **out, diff --git a/tests/commit/write.c b/tests/commit/write.c index d829c973d..2c2278546 100644 --- a/tests/commit/write.c +++ b/tests/commit/write.c @@ -299,19 +299,43 @@ void test_commit_write__can_validate_objects(void) cl_git_fail(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); } +void test_commit_write__attach_signature_checks_objects(void) +{ + const char *sig = "magic word: pretty please"; + const char *badtree = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +\n\ +a simple commit which does not work\n"; + + const char *badparent = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +\n\ +a simple commit which does not work\n"; + + git_oid id; + + cl_git_fail_with(-1, git_commit_create_with_signature(&id, g_repo, badtree, sig, "magicsig")); + cl_git_fail_with(-1, git_commit_create_with_signature(&id, g_repo, badparent, sig, "magicsig")); + +} + void test_commit_write__attach_singleline_signature(void) { const char *sig = "magic word: pretty please"; - const char *data = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ -parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ + const char *data = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ +parent 8496071c1b46c854b31185ea97743be6a8774479\n\ author Ben Burkert 1358451456 -0800\n\ committer Ben Burkert 1358451456 -0800\n\ \n\ a simple commit which works\n"; - const char *complete = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ -parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ + const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ +parent 8496071c1b46c854b31185ea97743be6a8774479\n\ author Ben Burkert 1358451456 -0800\n\ committer Ben Burkert 1358451456 -0800\n\ magicsig magic word: pretty please\n\ @@ -352,15 +376,15 @@ cpxtDQQMGYFpXK/71stq\n\ =ozeK\n\ -----END PGP SIGNATURE-----"; - const char *data = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ -parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ + const char *data = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ +parent 8496071c1b46c854b31185ea97743be6a8774479\n\ author Ben Burkert 1358451456 -0800\n\ committer Ben Burkert 1358451456 -0800\n\ \n\ a simple commit which works\n"; -const char *complete = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ -parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +const char *complete = "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n\ +parent 8496071c1b46c854b31185ea97743be6a8774479\n\ author Ben Burkert 1358451456 -0800\n\ committer Ben Burkert 1358451456 -0800\n\ gpgsig -----BEGIN PGP SIGNATURE-----\n\ diff --git a/tests/config/conditionals.c b/tests/config/conditionals.c index cb1f916ad..5858782c1 100644 --- a/tests/config/conditionals.c +++ b/tests/config/conditionals.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" +#include "repository.h" #ifdef GIT_WIN32 # define ROOT_PREFIX "C:" @@ -22,11 +23,11 @@ void test_config_conditionals__cleanup(void) static void assert_condition_includes(const char *keyword, const char *path, bool expected) { - git_config *cfg; git_buf buf = GIT_BUF_INIT; + git_config *cfg; - git_buf_printf(&buf, "[includeIf \"%s:%s\"]\n", keyword, path); - git_buf_puts(&buf, "path = other\n"); + cl_git_pass(git_buf_printf(&buf, "[includeIf \"%s:%s\"]\n", keyword, path)); + cl_git_pass(git_buf_puts(&buf, "path = other\n")); cl_git_mkfile("empty_standard_repo/.git/config", buf.ptr); cl_git_mkfile("empty_standard_repo/.git/other", "[foo]\nbar=baz\n"); @@ -47,53 +48,58 @@ static void assert_condition_includes(const char *keyword, const char *path, boo git_config_free(cfg); } +static char *sandbox_path(git_buf *buf, const char *suffix) +{ + char *path = p_realpath(clar_sandbox_path(), NULL); + cl_assert(path); + cl_git_pass(git_buf_attach(buf, path, 0)); + cl_git_pass(git_buf_joinpath(buf, buf->ptr, suffix)); + return buf->ptr; +} + void test_config_conditionals__gitdir(void) { git_buf path = GIT_BUF_INIT; - char *sandbox_path; assert_condition_includes("gitdir", ROOT_PREFIX "/", true); - assert_condition_includes("gitdir", "empty_standard_repo", true); + assert_condition_includes("gitdir", "empty_stand", false); + assert_condition_includes("gitdir", "empty_stand/", false); + assert_condition_includes("gitdir", "empty_stand/.git", false); + assert_condition_includes("gitdir", "empty_stand/.git/", false); + assert_condition_includes("gitdir", "empty_stand*/", true); + assert_condition_includes("gitdir", "empty_stand*/.git", true); + assert_condition_includes("gitdir", "empty_stand*/.git/", false); + assert_condition_includes("gitdir", "empty_standard_repo", false); assert_condition_includes("gitdir", "empty_standard_repo/", true); - assert_condition_includes("gitdir", "./", true); + assert_condition_includes("gitdir", "empty_standard_repo/.git", true); + assert_condition_includes("gitdir", "empty_standard_repo/.git/", false); + + assert_condition_includes("gitdir", "./", false); assert_condition_includes("gitdir", ROOT_PREFIX "/nonexistent", false); assert_condition_includes("gitdir", ROOT_PREFIX "/empty_standard_repo", false); - assert_condition_includes("gitdir", "empty_stand", false); assert_condition_includes("gitdir", "~/empty_standard_repo", false); - sandbox_path = p_realpath(clar_sandbox_path(), NULL); + assert_condition_includes("gitdir", sandbox_path(&path, "/"), true); + assert_condition_includes("gitdir", sandbox_path(&path, "/*"), false); + assert_condition_includes("gitdir", sandbox_path(&path, "/**"), true); - git_buf_joinpath(&path, sandbox_path, "/"); - assert_condition_includes("gitdir", path.ptr, true); + assert_condition_includes("gitdir", sandbox_path(&path, "empty_standard_repo"), false); + assert_condition_includes("gitdir", sandbox_path(&path, "empty_standard_repo/"), true); + assert_condition_includes("gitdir", sandbox_path(&path, "empty_standard_repo/"), true); + assert_condition_includes("gitdir", sandbox_path(&path, "Empty_Standard_Repo"), false); + assert_condition_includes("gitdir", sandbox_path(&path, "Empty_Standard_Repo/"), false); - git_buf_joinpath(&path, sandbox_path, "/*"); - assert_condition_includes("gitdir", path.ptr, true); - - git_buf_joinpath(&path, sandbox_path, "empty_standard_repo"); - assert_condition_includes("gitdir", path.ptr, true); - - git_buf_joinpath(&path, sandbox_path, "Empty_Standard_Repo"); - assert_condition_includes("gitdir", path.ptr, false); - - git__free(sandbox_path); git_buf_dispose(&path); } void test_config_conditionals__gitdir_i(void) { git_buf path = GIT_BUF_INIT; - char *sandbox_path; - sandbox_path = p_realpath(clar_sandbox_path(), NULL); + assert_condition_includes("gitdir/i", sandbox_path(&path, "empty_standard_repo/"), true); + assert_condition_includes("gitdir/i", sandbox_path(&path, "EMPTY_STANDARD_REPO/"), true); - git_buf_joinpath(&path, sandbox_path, "empty_standard_repo"); - assert_condition_includes("gitdir/i", path.ptr, true); - - git_buf_joinpath(&path, sandbox_path, "EMPTY_STANDARD_REPO"); - assert_condition_includes("gitdir/i", path.ptr, true); - - git__free(sandbox_path); git_buf_dispose(&path); } @@ -101,3 +107,42 @@ void test_config_conditionals__invalid_conditional_fails(void) { assert_condition_includes("foobar", ".git", false); } + +static void set_head(git_repository *repo, const char *name) +{ + cl_git_pass(git_repository_create_head(git_repository_path(repo), name)); +} + +void test_config_conditionals__onbranch(void) +{ + assert_condition_includes("onbranch", "master", true); + assert_condition_includes("onbranch", "m*", true); + assert_condition_includes("onbranch", "*", true); + assert_condition_includes("onbranch", "master/", false); + assert_condition_includes("onbranch", "foo", false); + + set_head(_repo, "foo"); + assert_condition_includes("onbranch", "master", false); + assert_condition_includes("onbranch", "foo", true); + assert_condition_includes("onbranch", "f*o", true); + + set_head(_repo, "dir/ref"); + assert_condition_includes("onbranch", "dir/ref", true); + assert_condition_includes("onbranch", "dir/", true); + assert_condition_includes("onbranch", "dir/*", true); + assert_condition_includes("onbranch", "dir/**", true); + assert_condition_includes("onbranch", "**", true); + assert_condition_includes("onbranch", "dir", false); + assert_condition_includes("onbranch", "dir*", false); + + set_head(_repo, "dir/subdir/ref"); + assert_condition_includes("onbranch", "dir/subdir/", true); + assert_condition_includes("onbranch", "dir/subdir/*", true); + assert_condition_includes("onbranch", "dir/subdir/ref", true); + assert_condition_includes("onbranch", "dir/", true); + assert_condition_includes("onbranch", "dir/**", true); + assert_condition_includes("onbranch", "**", true); + assert_condition_includes("onbranch", "dir", false); + assert_condition_includes("onbranch", "dir*", false); + assert_condition_includes("onbranch", "dir/*", false); +} diff --git a/tests/config/global.c b/tests/config/global.c index 9b9da6fb7..ed47d0251 100644 --- a/tests/config/global.c +++ b/tests/config/global.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" void test_config_global__initialize(void) { @@ -27,29 +27,61 @@ void test_config_global__initialize(void) void test_config_global__cleanup(void) { cl_sandbox_set_search_path_defaults(); + cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("xdg", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("etc", NULL, GIT_RMDIR_REMOVE_FILES)); } void test_config_global__open_global(void) { git_config *cfg, *global, *selected, *dummy; + int32_t value; + + cl_git_mkfile("home/.gitconfig", "[global]\n test = 4567\n"); cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_int32(&value, cfg, "global.test")); + cl_assert_equal_i(4567, value); + cl_git_pass(git_config_open_level(&global, cfg, GIT_CONFIG_LEVEL_GLOBAL)); + cl_git_pass(git_config_get_int32(&value, global, "global.test")); + cl_assert_equal_i(4567, value); + cl_git_fail(git_config_open_level(&dummy, cfg, GIT_CONFIG_LEVEL_XDG)); + cl_git_pass(git_config_open_global(&selected, cfg)); + cl_git_pass(git_config_get_int32(&value, selected, "global.test")); + cl_assert_equal_i(4567, value); git_config_free(selected); git_config_free(global); git_config_free(cfg); } +void test_config_global__open_symlinked_global(void) +{ +#ifndef GIT_WIN32 + git_config *cfg; + int32_t value; + + cl_git_mkfile("home/.gitconfig.linked", "[global]\n test = 4567\n"); + cl_must_pass(symlink(".gitconfig.linked", "home/.gitconfig")); + + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_int32(&value, cfg, "global.test")); + cl_assert_equal_i(4567, value); + + git_config_free(cfg); +#endif +} + void test_config_global__lock_missing_global_config(void) { git_config *cfg; git_config_entry *entry; git_transaction *transaction; - p_unlink("home/.gitconfig"); /* No global config */ + (void)p_unlink("home/.gitconfig"); /* No global config */ cl_git_pass(git_config_open_default(&cfg)); cl_git_pass(git_config_lock(&transaction, cfg)); diff --git a/tests/config/include.c b/tests/config/include.c index 44546933b..e2b0fc96c 100644 --- a/tests/config/include.c +++ b/tests/config/include.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" static git_config *cfg; static git_buf buf; @@ -202,3 +202,33 @@ void test_config_include__included_variables_cannot_be_modified(void) cl_git_pass(p_unlink("top-level")); cl_git_pass(p_unlink("included")); } + +void test_config_include__variables_in_included_override_including(void) +{ + int i; + + cl_git_mkfile("top-level", "[foo]\nbar = 1\n[include]\npath = included"); + cl_git_mkfile("included", "[foo]\nbar = 2"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_pass(git_config_get_int32(&i, cfg, "foo.bar")); + cl_assert_equal_i(i, 2); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("included")); +} + +void test_config_include__variables_in_including_override_included(void) +{ + int i; + + cl_git_mkfile("top-level", "[include]\npath = included\n[foo]\nbar = 1"); + cl_git_mkfile("included", "[foo]\nbar = 2"); + + cl_git_pass(git_config_open_ondisk(&cfg, "top-level")); + cl_git_pass(git_config_get_int32(&i, cfg, "foo.bar")); + cl_assert_equal_i(i, 1); + + cl_git_pass(p_unlink("top-level")); + cl_git_pass(p_unlink("included")); +} diff --git a/tests/config/new.c b/tests/config/new.c index bb3a2d9a8..22c330f6f 100644 --- a/tests/config/new.c +++ b/tests/config/new.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "filebuf.h" -#include "fileops.h" +#include "futils.h" #include "posix.h" #define TEST_CONFIG "git-new-config" @@ -30,5 +30,5 @@ void test_config_new__write_new_config(void) git_buf_dispose(&buf); git_config_free(config); - p_unlink(TEST_CONFIG); + cl_must_pass(p_unlink(TEST_CONFIG)); } diff --git a/tests/config/read.c b/tests/config/read.c index ccc479bc1..ba97302f7 100644 --- a/tests/config/read.c +++ b/tests/config/read.c @@ -779,6 +779,93 @@ void test_config_read__bom(void) git_buf_dispose(&buf); } +void test_config_read__arbitrary_whitespace_before_subsection(void) +{ + git_buf buf = GIT_BUF_INIT; + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some \t \"subsection\"]\n var = value\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig")); + cl_git_pass(git_config_get_string_buf(&buf, cfg, "some.subsection.var")); + cl_assert_equal_s(buf.ptr, "value"); + + git_config_free(cfg); + git_buf_dispose(&buf); +} + +void test_config_read__no_whitespace_after_subsection(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some \"subsection\" ]\n var = value\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__invalid_space_section(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some section]\n var = value\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__invalid_quoted_first_section(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[\"some\"]\n var = value\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__invalid_unquoted_subsection(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some sub section]\n var = value\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__invalid_quoted_third_section(void) +{ + git_config *cfg; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "\xEF\xBB\xBF[some sub \"section\"]\n var = value\n"); + cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig")); + + git_config_free(cfg); +} + +void test_config_read__unreadable_file_ignored(void) +{ + git_buf buf = GIT_BUF_INIT; + git_config *cfg; + int ret; + + cl_set_cleanup(&clean_test_config, NULL); + cl_git_mkfile("./testconfig", "[some] var = value\n[some \"OtheR\"] var = value"); + cl_git_pass(p_chmod("./testconfig", 0)); + + ret = git_config_open_ondisk(&cfg, "./test/config"); + cl_assert(ret == 0 || ret == GIT_ENOTFOUND); + + git_config_free(cfg); + git_buf_dispose(&buf); +} + void test_config_read__single_line(void) { git_buf buf = GIT_BUF_INIT; diff --git a/tests/config/snapshot.c b/tests/config/snapshot.c index 3ea07c118..5cc08a721 100644 --- a/tests/config/snapshot.c +++ b/tests/config/snapshot.c @@ -1,45 +1,51 @@ #include "clar_libgit2.h" +#include "config_backend.h" + +static git_config *cfg; +static git_config *snapshot; + +void test_config_snapshot__cleanup(void) +{ + git_config_free(cfg); + cfg = NULL; + git_config_free(snapshot); + snapshot = NULL; +} + void test_config_snapshot__create_snapshot(void) { - int32_t tmp; - git_config *cfg, *snapshot, *new_snapshot; - const char *filename = "config-ext-change"; + int32_t i; - cl_git_mkfile(filename, "[old]\nvalue = 5\n"); - - cl_git_pass(git_config_open_ondisk(&cfg, filename)); - - cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value")); - cl_assert_equal_i(5, tmp); + cl_git_mkfile("config", "[old]\nvalue = 5\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "config")); + cl_git_pass(git_config_get_int32(&i, cfg, "old.value")); + cl_assert_equal_i(5, i); cl_git_pass(git_config_snapshot(&snapshot, cfg)); /* Change the value on the file itself (simulate external process) */ - cl_git_mkfile(filename, "[old]\nvalue = 56\n"); + cl_git_mkfile("config", "[old]\nvalue = 56\n"); - cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value")); - cl_assert_equal_i(56, tmp); - - cl_git_pass(git_config_get_int32(&tmp, snapshot, "old.value")); - cl_assert_equal_i(5, tmp); + cl_git_pass(git_config_get_int32(&i, cfg, "old.value")); + cl_assert_equal_i(56, i); + cl_git_pass(git_config_get_int32(&i, snapshot, "old.value")); + cl_assert_equal_i(5, i); /* Change the value on the file itself (simulate external process) */ - cl_git_mkfile(filename, "[old]\nvalue = 999\n"); - - cl_git_pass(git_config_snapshot(&new_snapshot, cfg)); - - /* New snapshot should see new value */ - cl_git_pass(git_config_get_int32(&tmp, new_snapshot, "old.value")); - cl_assert_equal_i(999, tmp); + cl_git_mkfile("config", "[old]\nvalue = 999\n"); /* Old snapshot should still have the old value */ - cl_git_pass(git_config_get_int32(&tmp, snapshot, "old.value")); - cl_assert_equal_i(5, tmp); - - git_config_free(new_snapshot); + cl_git_pass(git_config_get_int32(&i, snapshot, "old.value")); + cl_assert_equal_i(5, i); + + /* New snapshot should see new value */ git_config_free(snapshot); - git_config_free(cfg); + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + cl_git_pass(git_config_get_int32(&i, snapshot, "old.value")); + cl_assert_equal_i(999, i); + + cl_git_pass(p_unlink("config")); } static int count_me(const git_config_entry *entry, void *payload) @@ -55,24 +61,79 @@ static int count_me(const git_config_entry *entry, void *payload) void test_config_snapshot__multivar(void) { - int count = 0; - git_config *cfg, *snapshot; - const char *filename = "config-file"; - - cl_git_mkfile(filename, "[old]\nvalue = 5\nvalue = 6\n"); - - cl_git_pass(git_config_open_ondisk(&cfg, filename)); - cl_git_pass(git_config_get_multivar_foreach(cfg, "old.value", NULL, count_me, &count)); - - cl_assert_equal_i(2, count); - - cl_git_pass(git_config_snapshot(&snapshot, cfg)); - git_config_free(cfg); + int count; count = 0; - cl_git_pass(git_config_get_multivar_foreach(snapshot, "old.value", NULL, count_me, &count)); - + cl_git_mkfile("config", "[old]\nvalue = 5\nvalue = 6\n"); + cl_git_pass(git_config_open_ondisk(&cfg, "config")); + cl_git_pass(git_config_get_multivar_foreach(cfg, "old.value", NULL, count_me, &count)); cl_assert_equal_i(2, count); - git_config_free(snapshot); + count = 0; + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + cl_git_pass(git_config_get_multivar_foreach(snapshot, "old.value", NULL, count_me, &count)); + cl_assert_equal_i(2, count); + + cl_git_pass(p_unlink("config")); +} + +void test_config_snapshot__includes(void) +{ + int i; + + cl_git_mkfile("including", "[include]\npath = included"); + cl_git_mkfile("included", "[section]\nkey = 1\n"); + + cl_git_pass(git_config_open_ondisk(&cfg, "including")); + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + + cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); + cl_assert_equal_i(i, 1); + + /* Rewrite "included" config */ + cl_git_mkfile("included", "[section]\nkey = 11\n"); + + /* Assert that the live config changed, but snapshot remained the same */ + cl_git_pass(git_config_get_int32(&i, cfg, "section.key")); + cl_assert_equal_i(i, 11); + cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); + cl_assert_equal_i(i, 1); + + cl_git_pass(p_unlink("including")); + cl_git_pass(p_unlink("included")); +} + +void test_config_snapshot__snapshot(void) +{ + git_config *snapshot_snapshot; + int i; + + cl_git_mkfile("configfile", "[section]\nkey = 1\n"); + + cl_git_pass(git_config_open_ondisk(&cfg, "configfile")); + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + + cl_git_pass(git_config_snapshot(&snapshot_snapshot, snapshot)); + + cl_git_pass(git_config_get_int32(&i, snapshot_snapshot, "section.key")); + cl_assert_equal_i(i, 1); + + git_config_free(snapshot_snapshot); + + cl_git_pass(p_unlink("configfile")); +} + +void test_config_snapshot__snapshot_from_in_memony(void) +{ + const char *configuration = "[section]\nkey = 1\n"; + git_config_backend *backend; + int i; + + cl_git_pass(git_config_new(&cfg)); + cl_git_pass(git_config_backend_from_string(&backend, configuration, strlen(configuration))); + cl_git_pass(git_config_add_backend(cfg, backend, 0, NULL, 0)); + + cl_git_pass(git_config_snapshot(&snapshot, cfg)); + cl_git_pass(git_config_get_int32(&i, snapshot, "section.key")); + cl_assert_equal_i(i, 1); } diff --git a/tests/config/stress.c b/tests/config/stress.c index d86a11d4a..28c3c4ac0 100644 --- a/tests/config/stress.c +++ b/tests/config/stress.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "filebuf.h" -#include "fileops.h" +#include "futils.h" #include "posix.h" #define TEST_CONFIG "git-test-config" @@ -123,6 +123,7 @@ void test_config_stress__quick_write(void) for (i = 0; i < 10; i++) { int32_t val; cl_git_pass(git_config_set_int32(config_w, key, i)); + cl_msleep(1); cl_git_pass(git_config_get_int32(&val, config_r, key)); cl_assert_equal_i(i, val); } @@ -130,3 +131,71 @@ void test_config_stress__quick_write(void) git_config_free(config_r); git_config_free(config_w); } + +static int foreach_cb(const git_config_entry *entry, void *payload) +{ + if (!strcmp(entry->name, "key.value")) { + *(char **)payload = git__strdup(entry->value); + return 0; + } + return -1; +} + +void test_config_stress__foreach_refreshes(void) +{ + git_config *config_w, *config_r; + char *value = NULL; + + cl_git_pass(git_config_open_ondisk(&config_w, "./cfg")); + cl_git_pass(git_config_open_ondisk(&config_r, "./cfg")); + + cl_git_pass(git_config_set_string(config_w, "key.value", "1")); + cl_git_pass(git_config_foreach_match(config_r, "key.value", foreach_cb, &value)); + + cl_assert_equal_s(value, "1"); + + git_config_free(config_r); + git_config_free(config_w); + git__free(value); +} + +void test_config_stress__foreach_refreshes_snapshot(void) +{ + git_config *config, *snapshot; + char *value = NULL; + + cl_git_pass(git_config_open_ondisk(&config, "./cfg")); + + cl_git_pass(git_config_set_string(config, "key.value", "1")); + cl_git_pass(git_config_snapshot(&snapshot, config)); + cl_git_pass(git_config_foreach_match(snapshot, "key.value", foreach_cb, &value)); + + cl_assert_equal_s(value, "1"); + + git_config_free(snapshot); + git_config_free(config); + git__free(value); +} + +void test_config_stress__huge_section_with_many_values(void) +{ + git_config *config; + + if (!cl_is_env_set("GITTEST_INVASIVE_SPEED")) + cl_skip(); + + /* + * The config file is structured in such a way that is + * has a section header that is approximately 500kb of + * size followed by 40k entries. While the resulting + * configuration file itself is roughly 650kb in size and + * thus considered to be rather small, in the past we'd + * balloon to more than 20GB of memory (20000x500kb) + * while parsing the file. It thus was a trivial way to + * cause an out-of-memory situation and thus cause denial + * of service, e.g. via gitmodules. + */ + cl_git_pass(git_config_open_ondisk(&config, cl_fixture("config/config-oom"))); + + git_config_free(config); +} diff --git a/tests/config/write.c b/tests/config/write.c index bd0f5b277..78ed7f15b 100644 --- a/tests/config/write.c +++ b/tests/config/write.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" #include "git2/sys/config.h" #include "config.h" diff --git a/tests/core/buffer.c b/tests/core/buffer.c index b8a76b39c..4e895afd9 100644 --- a/tests/core/buffer.c +++ b/tests/core/buffer.c @@ -2,7 +2,7 @@ #include "buffer.h" #include "buf_text.h" #include "git2/sys/hashsig.h" -#include "fileops.h" +#include "futils.h" #define TESTSTR "Have you seen that? Have you seeeen that??" const char *test_string = TESTSTR; @@ -231,9 +231,9 @@ check_buf_append( git_buf_puts(&tgt, data_b); cl_assert(git_buf_oom(&tgt) == 0); cl_assert_equal_s(expected_data, git_buf_cstr(&tgt)); - cl_assert(tgt.size == expected_size); + cl_assert_equal_i(tgt.size, expected_size); if (expected_asize > 0) - cl_assert(tgt.asize == expected_asize); + cl_assert_equal_i(tgt.asize, expected_asize); git_buf_dispose(&tgt); } @@ -308,7 +308,7 @@ void test_core_buffer__5(void) REP16("x") REP16("o"), 32, 40); check_buf_append(test_4096, "", test_4096, 4096, 4104); - check_buf_append(test_4096, test_4096, test_8192, 8192, 9240); + check_buf_append(test_4096, test_4096, test_8192, 8192, 8200); /* check sequences of appends */ check_buf_append_abc("a", "b", "c", @@ -1204,3 +1204,39 @@ void test_core_buffer__dont_grow_borrowed(void) cl_git_fail_with(GIT_EINVALID, git_buf_grow(&buf, 1024)); } + +void test_core_buffer__dont_hit_infinite_loop_when_resizing(void) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_puts(&buf, "foobar")); + /* + * We do not care whether this succeeds or fails, which + * would depend on platform-specific allocation + * semantics. We only want to know that the function + * actually returns. + */ + (void)git_buf_try_grow(&buf, SIZE_MAX, true); + + git_buf_dispose(&buf); +} + +void test_core_buffer__avoid_printing_into_oom_buffer(void) +{ + git_buf buf = GIT_BUF_INIT; + + /* Emulate OOM situation with a previous allocation */ + buf.asize = 8; + buf.ptr = git_buf__oom; + + /* + * Print the same string again. As the buffer still has + * an `asize` of 8 due to the previous print, + * `ENSURE_SIZE` would not try to reallocate the array at + * all. As it didn't explicitly check for `git_buf__oom` + * in earlier versions, this would've resulted in it + * returning successfully and thus `git_buf_puts` would + * just print into the `git_buf__oom` array. + */ + cl_git_fail(git_buf_puts(&buf, "foobar")); +} diff --git a/tests/core/copy.c b/tests/core/copy.c index 967748cc5..d31274808 100644 --- a/tests/core/copy.c +++ b/tests/core/copy.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "path.h" #include "posix.h" diff --git a/tests/core/dirent.c b/tests/core/dirent.c index a2448b498..08e0b11cf 100644 --- a/tests/core/dirent.c +++ b/tests/core/dirent.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" typedef struct name_data { int count; /* return count */ diff --git a/tests/core/env.c b/tests/core/env.c index 7e7b3274d..3c34b2c4d 100644 --- a/tests/core/env.c +++ b/tests/core/env.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "sysdir.h" #include "path.h" diff --git a/tests/core/filebuf.c b/tests/core/filebuf.c index 8d1952f57..e527ce97c 100644 --- a/tests/core/filebuf.c +++ b/tests/core/filebuf.c @@ -157,9 +157,8 @@ void test_core_filebuf__symlink_follow(void) git_filebuf file = GIT_FILEBUF_INIT; const char *dir = "linkdir", *source = "linkdir/link"; -#ifdef GIT_WIN32 - cl_skip(); -#endif + if (!git_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); cl_git_pass(p_mkdir(dir, 0777)); cl_git_pass(p_symlink("target", source)); @@ -192,9 +191,8 @@ void test_core_filebuf__symlink_follow_absolute_paths(void) git_filebuf file = GIT_FILEBUF_INIT; git_buf source = GIT_BUF_INIT, target = GIT_BUF_INIT; -#ifdef GIT_WIN32 - cl_skip(); -#endif + if (!git_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); cl_git_pass(git_buf_joinpath(&source, clar_sandbox_path(), "linkdir/link")); cl_git_pass(git_buf_joinpath(&target, clar_sandbox_path(), "linkdir/target")); @@ -221,9 +219,8 @@ void test_core_filebuf__symlink_depth(void) git_filebuf file = GIT_FILEBUF_INIT; const char *dir = "linkdir", *source = "linkdir/link"; -#ifdef GIT_WIN32 - cl_skip(); -#endif + if (!git_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); cl_git_pass(p_mkdir(dir, 0777)); /* Endless loop */ diff --git a/tests/core/ftruncate.c b/tests/core/ftruncate.c index 2f4729fc2..0c731cb1e 100644 --- a/tests/core/ftruncate.c +++ b/tests/core/ftruncate.c @@ -27,7 +27,7 @@ void test_core_ftruncate__cleanup(void) p_unlink(filename); } -static void _extend(git_off_t i64len) +static void _extend(off64_t i64len) { struct stat st; int error; diff --git a/tests/core/futils.c b/tests/core/futils.c index fce4848f5..2c8294c87 100644 --- a/tests/core/futils.c +++ b/tests/core/futils.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" /* Fixture setup and teardown */ void test_core_futils__initialize(void) @@ -66,3 +66,24 @@ void test_core_futils__write_hidden_file(void) #endif } +void test_core_futils__recursive_rmdir_keeps_symlink_targets(void) +{ + if (!git_path_supports_symlinks(clar_sandbox_path())) + cl_skip(); + + cl_git_pass(git_futils_mkdir_r("a/b", 0777)); + cl_git_pass(git_futils_mkdir_r("dir-target", 0777)); + cl_git_mkfile("dir-target/file", "Contents"); + cl_git_mkfile("file-target", "Contents"); + cl_must_pass(p_symlink("dir-target", "a/symlink")); + cl_must_pass(p_symlink("file-target", "a/b/symlink")); + + cl_git_pass(git_futils_rmdir_r("a", NULL, GIT_RMDIR_REMOVE_FILES)); + + cl_assert(git_path_exists("dir-target")); + cl_assert(git_path_exists("file-target")); + + cl_must_pass(p_unlink("dir-target/file")); + cl_must_pass(p_rmdir("dir-target")); + cl_must_pass(p_unlink("file-target")); +} diff --git a/tests/core/link.c b/tests/core/link.c index 8cd6b01a9..1e5ed454c 100644 --- a/tests/core/link.c +++ b/tests/core/link.c @@ -54,7 +54,7 @@ static void do_symlink(const char *old, const char *new, int is_dir) create_symlink_func pCreateSymbolicLink; cl_assert(module = GetModuleHandle("kernel32")); - cl_assert(pCreateSymbolicLink = (create_symlink_func)GetProcAddress(module, "CreateSymbolicLinkA")); + cl_assert(pCreateSymbolicLink = (create_symlink_func)(void *)GetProcAddress(module, "CreateSymbolicLinkA")); cl_win32_pass(pCreateSymbolicLink(new, old, is_dir)); #endif @@ -70,7 +70,7 @@ static void do_hardlink(const char *old, const char *new) create_hardlink_func pCreateHardLink; cl_assert(module = GetModuleHandle("kernel32")); - cl_assert(pCreateHardLink = (create_hardlink_func)GetProcAddress(module, "CreateHardLinkA")); + cl_assert(pCreateHardLink = (create_hardlink_func)(void *)GetProcAddress(module, "CreateHardLinkA")); cl_win32_pass(pCreateHardLink(new, old, 0)); #endif diff --git a/tests/core/mkdir.c b/tests/core/mkdir.c index 8e52efb1e..ce11953f0 100644 --- a/tests/core/mkdir.c +++ b/tests/core/mkdir.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "path.h" #include "posix.h" diff --git a/tests/core/oidmap.c b/tests/core/oidmap.c index b5f6f99e1..7f98287a6 100644 --- a/tests/core/oidmap.c +++ b/tests/core/oidmap.c @@ -1,108 +1,127 @@ #include "clar_libgit2.h" #include "oidmap.h" -typedef struct { +static struct { git_oid oid; size_t extra; -} oidmap_item; +} test_oids[0x0FFF]; -#define NITEMS 0x0fff +static git_oidmap *g_map; + +void test_core_oidmap__initialize(void) +{ + uint32_t i, j; + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { + uint32_t segment = i / 8; + int modi = i - (segment * 8); + + test_oids[i].extra = i; + + for (j = 0; j < GIT_OID_RAWSZ / 4; ++j) { + test_oids[i].oid.id[j * 4 ] = (unsigned char)modi; + test_oids[i].oid.id[j * 4 + 1] = (unsigned char)(modi >> 8); + test_oids[i].oid.id[j * 4 + 2] = (unsigned char)(modi >> 16); + test_oids[i].oid.id[j * 4 + 3] = (unsigned char)(modi >> 24); + } + + test_oids[i].oid.id[ 8] = (unsigned char)i; + test_oids[i].oid.id[ 9] = (unsigned char)(i >> 8); + test_oids[i].oid.id[10] = (unsigned char)(i >> 16); + test_oids[i].oid.id[11] = (unsigned char)(i >> 24); + } + + cl_git_pass(git_oidmap_new(&g_map)); +} + +void test_core_oidmap__cleanup(void) +{ + git_oidmap_free(g_map); +} void test_core_oidmap__basic(void) { - git_oidmap *map; - oidmap_item items[NITEMS]; - uint32_t i, j; + size_t i; - for (i = 0; i < NITEMS; ++i) { - items[i].extra = i; - for (j = 0; j < GIT_OID_RAWSZ / 4; ++j) { - items[i].oid.id[j * 4 ] = (unsigned char)i; - items[i].oid.id[j * 4 + 1] = (unsigned char)(i >> 8); - items[i].oid.id[j * 4 + 2] = (unsigned char)(i >> 16); - items[i].oid.id[j * 4 + 3] = (unsigned char)(i >> 24); - } + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { + cl_assert(!git_oidmap_exists(g_map, &test_oids[i].oid)); + cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); } - map = git_oidmap_alloc(); - cl_assert(map != NULL); - - for (i = 0; i < NITEMS; ++i) { - size_t pos; - int ret; - - pos = git_oidmap_lookup_index(map, &items[i].oid); - cl_assert(!git_oidmap_valid_index(map, pos)); - - pos = git_oidmap_put(map, &items[i].oid, &ret); - cl_assert(ret != 0); - - git_oidmap_set_value_at(map, pos, &items[i]); + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { + cl_assert(git_oidmap_exists(g_map, &test_oids[i].oid)); + cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); } - - - for (i = 0; i < NITEMS; ++i) { - size_t pos; - - pos = git_oidmap_lookup_index(map, &items[i].oid); - cl_assert(git_oidmap_valid_index(map, pos)); - - cl_assert_equal_p(git_oidmap_value_at(map, pos), &items[i]); - } - - git_oidmap_free(map); } void test_core_oidmap__hash_collision(void) { - git_oidmap *map; - oidmap_item items[NITEMS]; - uint32_t i, j; + size_t i; - for (i = 0; i < NITEMS; ++i) { - uint32_t segment = i / 8; - int modi = i - (segment * 8); - - items[i].extra = i; - - for (j = 0; j < GIT_OID_RAWSZ / 4; ++j) { - items[i].oid.id[j * 4 ] = (unsigned char)modi; - items[i].oid.id[j * 4 + 1] = (unsigned char)(modi >> 8); - items[i].oid.id[j * 4 + 2] = (unsigned char)(modi >> 16); - items[i].oid.id[j * 4 + 3] = (unsigned char)(modi >> 24); - } - - items[i].oid.id[ 8] = (unsigned char)i; - items[i].oid.id[ 9] = (unsigned char)(i >> 8); - items[i].oid.id[10] = (unsigned char)(i >> 16); - items[i].oid.id[11] = (unsigned char)(i >> 24); + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { + cl_assert(!git_oidmap_exists(g_map, &test_oids[i].oid)); + cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); } - map = git_oidmap_alloc(); - cl_assert(map != NULL); - - for (i = 0; i < NITEMS; ++i) { - size_t pos; - int ret; - - pos = git_oidmap_lookup_index(map, &items[i].oid); - cl_assert(!git_oidmap_valid_index(map, pos)); - - pos = git_oidmap_put(map, &items[i].oid, &ret); - cl_assert(ret != 0); - - git_oidmap_set_value_at(map, pos, &items[i]); + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) { + cl_assert(git_oidmap_exists(g_map, &test_oids[i].oid)); + cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); } - - - for (i = 0; i < NITEMS; ++i) { - size_t pos; - - pos = git_oidmap_lookup_index(map, &items[i].oid); - cl_assert(git_oidmap_valid_index(map, pos)); - - cl_assert_equal_p(git_oidmap_value_at(map, pos), &items[i]); - } - - git_oidmap_free(map); +} + +void test_core_oidmap__get_succeeds_with_existing_keys(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) + cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); + + for (i = 0; i < ARRAY_SIZE(test_oids); ++i) + cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[i].oid), &test_oids[i]); +} + +void test_core_oidmap__get_fails_with_nonexisting_key(void) +{ + size_t i; + + /* Do _not_ add last OID to verify that we cannot look it up */ + for (i = 0; i < ARRAY_SIZE(test_oids) - 1; ++i) + cl_git_pass(git_oidmap_set(g_map, &test_oids[i].oid, &test_oids[i])); + + cl_assert_equal_p(git_oidmap_get(g_map, &test_oids[ARRAY_SIZE(test_oids) - 1].oid), NULL); +} + +void test_core_oidmap__setting_oid_persists(void) +{ + git_oid oids[] = { + {{ 0x01 }}, + {{ 0x02 }}, + {{ 0x03 }} + }; + + cl_git_pass(git_oidmap_set(g_map, &oids[0], "one")); + cl_git_pass(git_oidmap_set(g_map, &oids[1], "two")); + cl_git_pass(git_oidmap_set(g_map, &oids[2], "three")); + + cl_assert_equal_s(git_oidmap_get(g_map, &oids[0]), "one"); + cl_assert_equal_s(git_oidmap_get(g_map, &oids[1]), "two"); + cl_assert_equal_s(git_oidmap_get(g_map, &oids[2]), "three"); +} + +void test_core_oidmap__setting_existing_key_updates(void) +{ + git_oid oids[] = { + {{ 0x01 }}, + {{ 0x02 }}, + {{ 0x03 }} + }; + + cl_git_pass(git_oidmap_set(g_map, &oids[0], "one")); + cl_git_pass(git_oidmap_set(g_map, &oids[1], "two")); + cl_git_pass(git_oidmap_set(g_map, &oids[2], "three")); + cl_assert_equal_i(git_oidmap_size(g_map), 3); + + cl_git_pass(git_oidmap_set(g_map, &oids[1], "other")); + cl_assert_equal_i(git_oidmap_size(g_map), 3); + + cl_assert_equal_s(git_oidmap_get(g_map, &oids[1]), "other"); } diff --git a/tests/core/path.c b/tests/core/path.c index 058a710d0..2e5a4ab24 100644 --- a/tests/core/path.c +++ b/tests/core/path.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" static void check_dirname(const char *A, const char *B) diff --git a/tests/core/posix.c b/tests/core/posix.c index 172462073..1bb1e9c6b 100644 --- a/tests/core/posix.c +++ b/tests/core/posix.c @@ -9,11 +9,9 @@ # endif #endif -#include - #include "clar_libgit2.h" +#include "futils.h" #include "posix.h" -#include "userdiff.h" void test_core_posix__initialize(void) { @@ -114,7 +112,7 @@ void test_core_posix__utimes(void) cl_git_mkfile("foo", "Dummy file."); cl_must_pass(p_utimes("foo", times)); - p_stat("foo", &st); + cl_must_pass(p_stat("foo", &st)); cl_assert_equal_i(1234567890, st.st_atime); cl_assert_equal_i(1234567890, st.st_mtime); @@ -127,9 +125,9 @@ void test_core_posix__utimes(void) cl_must_pass(fd = p_open("foo", O_RDWR)); cl_must_pass(p_futimes(fd, times)); - p_close(fd); + cl_must_pass(p_close(fd)); - p_stat("foo", &st); + cl_must_pass(p_stat("foo", &st)); cl_assert_equal_i(1414141414, st.st_atime); cl_assert_equal_i(1414141414, st.st_mtime); @@ -140,53 +138,101 @@ void test_core_posix__utimes(void) cl_must_pass(p_utimes("foo", NULL)); curtime = time(NULL); - p_stat("foo", &st); + cl_must_pass(p_stat("foo", &st)); cl_assert((st.st_atime - curtime) < 5); cl_assert((st.st_mtime - curtime) < 5); - p_unlink("foo"); + cl_must_pass(p_unlink("foo")); } -void test_core_posix__p_regcomp_ignores_global_locale_ctype(void) +void test_core_posix__unlink_removes_symlink(void) { - regex_t preg; - int error = 0; + if (!git_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); - const char* oldlocale = setlocale(LC_CTYPE, NULL); + cl_git_mkfile("file", "Dummy file."); + cl_git_pass(git_futils_mkdir("dir", 0777, 0)); - if (!setlocale(LC_CTYPE, "UTF-8") && - !setlocale(LC_CTYPE, "c.utf8") && - !setlocale(LC_CTYPE, "en_US.UTF-8")) - cl_skip(); + cl_must_pass(p_symlink("file", "file-symlink")); + cl_must_pass(p_symlink("dir", "dir-symlink")); - if (MB_CUR_MAX == 1) { - setlocale(LC_CTYPE, oldlocale); - cl_fail("Expected locale to be switched to multibyte"); - } + cl_must_pass(p_unlink("file-symlink")); + cl_must_pass(p_unlink("dir-symlink")); - p_regcomp(&preg, "[\xc0-\xff][\x80-\xbf]", REG_EXTENDED); - regfree(&preg); + cl_assert(git_path_exists("file")); + cl_assert(git_path_exists("dir")); - setlocale(LC_CTYPE, oldlocale); - - cl_must_pass(error); + cl_must_pass(p_unlink("file")); + cl_must_pass(p_rmdir("dir")); } -void test_core_posix__p_regcomp_compile_userdiff_regexps(void) +void test_core_posix__symlink_resolves_to_correct_type(void) { - size_t idx; + git_buf contents = GIT_BUF_INIT; - for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) { - git_diff_driver_definition ddef = builtin_defs[idx]; - int error = 0; - regex_t preg; + if (!git_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); - error = p_regcomp(&preg, ddef.fns, REG_EXTENDED | ddef.flags); - regfree(&preg); - cl_must_pass(error); + cl_must_pass(git_futils_mkdir("dir", 0777, 0)); + cl_must_pass(git_futils_mkdir("file", 0777, 0)); + cl_git_mkfile("dir/file", "symlink target"); - error = p_regcomp(&preg, ddef.words, REG_EXTENDED); - regfree(&preg); - cl_must_pass(error); - } + cl_git_pass(p_symlink("file", "dir/link")); + + cl_git_pass(git_futils_readbuffer(&contents, "dir/file")); + cl_assert_equal_s(contents.ptr, "symlink target"); + + cl_must_pass(p_unlink("dir/link")); + cl_must_pass(p_unlink("dir/file")); + cl_must_pass(p_rmdir("dir")); + cl_must_pass(p_rmdir("file")); + + git_buf_dispose(&contents); +} + +void test_core_posix__relative_symlink(void) +{ + git_buf contents = GIT_BUF_INIT; + + if (!git_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); + + cl_must_pass(git_futils_mkdir("dir", 0777, 0)); + cl_git_mkfile("file", "contents"); + cl_git_pass(p_symlink("../file", "dir/link")); + cl_git_pass(git_futils_readbuffer(&contents, "dir/link")); + cl_assert_equal_s(contents.ptr, "contents"); + + cl_must_pass(p_unlink("file")); + cl_must_pass(p_unlink("dir/link")); + cl_must_pass(p_rmdir("dir")); + + git_buf_dispose(&contents); +} + +void test_core_posix__symlink_to_file_across_dirs(void) +{ + git_buf contents = GIT_BUF_INIT; + + if (!git_path_supports_symlinks(clar_sandbox_path())) + clar__skip(); + + /* + * Create a relative symlink that points into another + * directory. This used to not work on Win32, where we + * forgot to convert directory separators to + * Windows-style ones. + */ + cl_must_pass(git_futils_mkdir("dir", 0777, 0)); + cl_git_mkfile("dir/target", "symlink target"); + cl_git_pass(p_symlink("dir/target", "link")); + + cl_git_pass(git_futils_readbuffer(&contents, "dir/target")); + cl_assert_equal_s(contents.ptr, "symlink target"); + + cl_must_pass(p_unlink("dir/target")); + cl_must_pass(p_unlink("link")); + cl_must_pass(p_rmdir("dir")); + + git_buf_dispose(&contents); } diff --git a/tests/core/qsort.c b/tests/core/qsort.c new file mode 100644 index 000000000..efb79e6e9 --- /dev/null +++ b/tests/core/qsort.c @@ -0,0 +1,90 @@ +#include "clar_libgit2.h" + +#define assert_sorted(a, cmp) \ + _assert_sorted(a, ARRAY_SIZE(a), sizeof(*a), cmp) + +struct big_entries { + char c[311]; +}; + +static void _assert_sorted(void *els, size_t n, size_t elsize, git__sort_r_cmp cmp) +{ + int8_t *p = els; + + git__qsort_r(p, n, elsize, cmp, NULL); + while (n-- > 1) { + cl_assert(cmp(p, p + elsize, NULL) <= 0); + p += elsize; + } +} + +static int cmp_big(const void *_a, const void *_b, void *payload) +{ + const struct big_entries *a = (const struct big_entries *)_a, *b = (const struct big_entries *)_b; + GIT_UNUSED(payload); + return (a->c[0] < b->c[0]) ? -1 : (a->c[0] > b->c[0]) ? +1 : 0; +} + +static int cmp_int(const void *_a, const void *_b, void *payload) +{ + int a = *(const int *)_a, b = *(const int *)_b; + GIT_UNUSED(payload); + return (a < b) ? -1 : (a > b) ? +1 : 0; +} + +static int cmp_str(const void *_a, const void *_b, void *payload) +{ + GIT_UNUSED(payload); + return strcmp((const char *) _a, (const char *) _b); +} + +void test_core_qsort__array_with_single_entry(void) +{ + int a[] = { 10 }; + assert_sorted(a, cmp_int); +} + +void test_core_qsort__array_with_equal_entries(void) +{ + int a[] = { 4, 4, 4, 4 }; + assert_sorted(a, cmp_int); +} + +void test_core_qsort__sorted_array(void) +{ + int a[] = { 1, 10 }; + assert_sorted(a, cmp_int); +} + +void test_core_qsort__unsorted_array(void) +{ + int a[] = { 123, 9, 412938, 10, 234, 89 }; + assert_sorted(a, cmp_int); +} + +void test_core_qsort__sorting_strings(void) +{ + char *a[] = { "foo", "bar", "baz" }; + assert_sorted(a, cmp_str); +} + +void test_core_qsort__sorting_big_entries(void) +{ + struct big_entries a[5]; + + memset(&a, 0, sizeof(a)); + + memset(a[0].c, 'w', sizeof(a[0].c) - 1); + memset(a[1].c, 'c', sizeof(a[1].c) - 1); + memset(a[2].c, 'w', sizeof(a[2].c) - 1); + memset(a[3].c, 'h', sizeof(a[3].c) - 1); + memset(a[4].c, 'a', sizeof(a[4].c) - 1); + + assert_sorted(a, cmp_big); + + cl_assert_equal_i(strspn(a[0].c, "a"), sizeof(a[0].c) - 1); + cl_assert_equal_i(strspn(a[1].c, "c"), sizeof(a[1].c) - 1); + cl_assert_equal_i(strspn(a[2].c, "h"), sizeof(a[2].c) - 1); + cl_assert_equal_i(strspn(a[3].c, "w"), sizeof(a[3].c) - 1); + cl_assert_equal_i(strspn(a[4].c, "w"), sizeof(a[4].c) - 1); +} diff --git a/tests/core/regexp.c b/tests/core/regexp.c new file mode 100644 index 000000000..8db5641e5 --- /dev/null +++ b/tests/core/regexp.c @@ -0,0 +1,213 @@ +#include "clar_libgit2.h" + +#include + +#include "regexp.h" +#include "userdiff.h" + +#if LC_ALL > 0 +static const char *old_locales[LC_ALL]; +#endif + +static git_regexp regex; + +void test_core_regexp__initialize(void) +{ +#if LC_ALL > 0 + memset(&old_locales, 0, sizeof(old_locales)); +#endif +} + +void test_core_regexp__cleanup(void) +{ + git_regexp_dispose(®ex); +} + +static void try_set_locale(int category) +{ +#if LC_ALL > 0 + old_locales[category] = setlocale(category, NULL); +#endif + + if (!setlocale(category, "UTF-8") && + !setlocale(category, "c.utf8") && + !setlocale(category, "en_US.UTF-8")) + cl_skip(); + + if (MB_CUR_MAX == 1) + cl_fail("Expected locale to be switched to multibyte"); +} + + +void test_core_regexp__compile_ignores_global_locale_ctype(void) +{ + try_set_locale(LC_CTYPE); + cl_git_pass(git_regexp_compile(®ex, "[\xc0-\xff][\x80-\xbf]", 0)); +} + +void test_core_regexp__compile_ignores_global_locale_collate(void) +{ +#ifdef GIT_WIN32 + cl_skip(); +#endif + + try_set_locale(LC_COLLATE); + cl_git_pass(git_regexp_compile(®ex, "[\xc0-\xff][\x80-\xbf]", 0)); +} + +void test_core_regexp__regex_matches_digits_with_locale(void) +{ + char c, str[2]; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + try_set_locale(LC_COLLATE); + try_set_locale(LC_CTYPE); + + cl_git_pass(git_regexp_compile(®ex, "[[:digit:]]", 0)); + + str[1] = '\0'; + for (c = '0'; c <= '9'; c++) { + str[0] = c; + cl_git_pass(git_regexp_match(®ex, str)); + } +} + +void test_core_regexp__regex_matches_alphabet_with_locale(void) +{ + char c, str[2]; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + try_set_locale(LC_COLLATE); + try_set_locale(LC_CTYPE); + + cl_git_pass(git_regexp_compile(®ex, "[[:alpha:]]", 0)); + + str[1] = '\0'; + for (c = 'a'; c <= 'z'; c++) { + str[0] = c; + cl_git_pass(git_regexp_match(®ex, str)); + } + for (c = 'A'; c <= 'Z'; c++) { + str[0] = c; + cl_git_pass(git_regexp_match(®ex, str)); + } +} + +void test_core_regexp__compile_userdiff_regexps(void) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) { + git_diff_driver_definition ddef = builtin_defs[idx]; + + cl_git_pass(git_regexp_compile(®ex, ddef.fns, ddef.flags)); + git_regexp_dispose(®ex); + + cl_git_pass(git_regexp_compile(®ex, ddef.words, 0)); + git_regexp_dispose(®ex); + } +} + +void test_core_regexp__simple_search_matches(void) +{ + cl_git_pass(git_regexp_compile(®ex, "a", 0)); + cl_git_pass(git_regexp_search(®ex, "a", 0, NULL)); +} + +void test_core_regexp__case_insensitive_search_matches(void) +{ + cl_git_pass(git_regexp_compile(®ex, "a", GIT_REGEXP_ICASE)); + cl_git_pass(git_regexp_search(®ex, "A", 0, NULL)); +} + +void test_core_regexp__nonmatching_search_returns_error(void) +{ + cl_git_pass(git_regexp_compile(®ex, "a", 0)); + cl_git_fail(git_regexp_search(®ex, "b", 0, NULL)); +} + +void test_core_regexp__search_finds_complete_match(void) +{ + git_regmatch matches[1]; + + cl_git_pass(git_regexp_compile(®ex, "abc", 0)); + cl_git_pass(git_regexp_search(®ex, "abc", 1, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 3); +} + +void test_core_regexp__search_finds_correct_offsets(void) +{ + git_regmatch matches[3]; + + cl_git_pass(git_regexp_compile(®ex, "(a*)(b*)", 0)); + cl_git_pass(git_regexp_search(®ex, "ab", 3, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 2); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, 1); + cl_assert_equal_i(matches[2].end, 2); +} + +void test_core_regexp__search_finds_empty_group(void) +{ + git_regmatch matches[3]; + + cl_git_pass(git_regexp_compile(®ex, "(a*)(b*)c", 0)); + cl_git_pass(git_regexp_search(®ex, "ac", 3, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 2); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, 1); + cl_assert_equal_i(matches[2].end, 1); +} + +void test_core_regexp__search_fills_matches_with_first_matching_groups(void) +{ + git_regmatch matches[2]; + + cl_git_pass(git_regexp_compile(®ex, "(a)(b)(c)", 0)); + cl_git_pass(git_regexp_search(®ex, "abc", 2, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 3); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); +} + +void test_core_regexp__search_skips_nonmatching_group(void) +{ + git_regmatch matches[4]; + + cl_git_pass(git_regexp_compile(®ex, "(a)(b)?(c)", 0)); + cl_git_pass(git_regexp_search(®ex, "ac", 4, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 2); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, -1); + cl_assert_equal_i(matches[2].end, -1); + cl_assert_equal_i(matches[3].start, 1); + cl_assert_equal_i(matches[3].end, 2); +} + +void test_core_regexp__search_initializes_trailing_nonmatching_groups(void) +{ + git_regmatch matches[3]; + + cl_git_pass(git_regexp_compile(®ex, "(a)bc", 0)); + cl_git_pass(git_regexp_search(®ex, "abc", 3, matches)); + cl_assert_equal_i(matches[0].start, 0); + cl_assert_equal_i(matches[0].end, 3); + cl_assert_equal_i(matches[1].start, 0); + cl_assert_equal_i(matches[1].end, 1); + cl_assert_equal_i(matches[2].start, -1); + cl_assert_equal_i(matches[2].end, -1); +} diff --git a/tests/core/rmdir.c b/tests/core/rmdir.c index e00ec5c72..b436b97e0 100644 --- a/tests/core/rmdir.c +++ b/tests/core/rmdir.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" static const char *empty_tmp_dir = "test_gitfo_rmdir_recurs_test"; diff --git a/tests/core/stat.c b/tests/core/stat.c index 59a134649..7f5d66753 100644 --- a/tests/core/stat.c +++ b/tests/core/stat.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "path.h" #include "posix.h" diff --git a/tests/core/strmap.c b/tests/core/strmap.c index 64f516452..ba118ae1e 100644 --- a/tests/core/strmap.c +++ b/tests/core/strmap.c @@ -1,11 +1,11 @@ #include "clar_libgit2.h" #include "strmap.h" -git_strmap *g_table; +static git_strmap *g_table; void test_core_strmap__initialize(void) { - cl_git_pass(git_strmap_alloc(&g_table)); + cl_git_pass(git_strmap_new(&g_table)); cl_assert(g_table != NULL); } @@ -16,12 +16,12 @@ void test_core_strmap__cleanup(void) void test_core_strmap__0(void) { - cl_assert(git_strmap_num_entries(g_table) == 0); + cl_assert(git_strmap_size(g_table) == 0); } -static void insert_strings(git_strmap *table, int count) +static void insert_strings(git_strmap *table, size_t count) { - int i, j, over, err; + size_t i, j, over; char *str; for (i = 0; i < count; ++i) { @@ -34,17 +34,16 @@ static void insert_strings(git_strmap *table, int count) for (j = 0, over = i / 26; over > 0; j++, over = over / 26) str[j] = 'A' + (over % 26); - git_strmap_insert(table, str, str, &err); - cl_assert(err >= 0); + cl_git_pass(git_strmap_set(table, str, str)); } - cl_assert((int)git_strmap_num_entries(table) == count); + cl_assert_equal_i(git_strmap_size(table), count); } -void test_core_strmap__1(void) +void test_core_strmap__inserted_strings_can_be_retrieved(void) { - int i; char *str; + int i; insert_strings(g_table, 20); @@ -58,41 +57,134 @@ void test_core_strmap__1(void) cl_assert(i == 20); } -void test_core_strmap__2(void) +void test_core_strmap__deleted_entry_cannot_be_retrieved(void) { - size_t pos; - int i; char *str; + int i; insert_strings(g_table, 20); - cl_assert(git_strmap_exists(g_table, "aaaaaaaaa")); - cl_assert(git_strmap_exists(g_table, "ggggggggg")); - cl_assert(!git_strmap_exists(g_table, "aaaaaaaab")); - cl_assert(!git_strmap_exists(g_table, "abcdefghi")); - cl_assert(git_strmap_exists(g_table, "bbbbbbbbb")); - pos = git_strmap_lookup_index(g_table, "bbbbbbbbb"); - cl_assert(git_strmap_valid_index(g_table, pos)); - cl_assert_equal_s(git_strmap_value_at(g_table, pos), "bbbbbbbbb"); - free(git_strmap_value_at(g_table, pos)); - git_strmap_delete_at(g_table, pos); + str = git_strmap_get(g_table, "bbbbbbbbb"); + cl_assert_equal_s(str, "bbbbbbbbb"); + cl_git_pass(git_strmap_delete(g_table, "bbbbbbbbb")); + free(str); cl_assert(!git_strmap_exists(g_table, "bbbbbbbbb")); i = 0; git_strmap_foreach_value(g_table, str, { i++; free(str); }); - cl_assert(i == 19); + cl_assert_equal_i(i, 19); } -void test_core_strmap__3(void) +void test_core_strmap__inserting_many_keys_succeeds(void) { - int i; char *str; + int i; insert_strings(g_table, 10000); i = 0; git_strmap_foreach_value(g_table, str, { i++; free(str); }); - cl_assert(i == 10000); + cl_assert_equal_i(i, 10000); +} + +void test_core_strmap__get_succeeds_with_existing_entries(void) +{ + const char *keys[] = { "foo", "bar", "gobble" }; + char *values[] = { "oof", "rab", "elbbog" }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(keys); i++) + cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); + + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); + cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); + cl_assert_equal_s(git_strmap_get(g_table, "gobble"), "elbbog"); +} + +void test_core_strmap__get_returns_null_on_nonexisting_key(void) +{ + const char *keys[] = { "foo", "bar", "gobble" }; + char *values[] = { "oof", "rab", "elbbog" }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(keys); i++) + cl_git_pass(git_strmap_set(g_table, keys[i], values[i])); + + cl_assert_equal_p(git_strmap_get(g_table, "other"), NULL); +} + +void test_core_strmap__set_persists_key(void) +{ + cl_git_pass(git_strmap_set(g_table, "foo", "oof")); + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); +} + +void test_core_strmap__set_persists_multpile_keys(void) +{ + cl_git_pass(git_strmap_set(g_table, "foo", "oof")); + cl_git_pass(git_strmap_set(g_table, "bar", "rab")); + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof"); + cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab"); +} + +void test_core_strmap__set_updates_existing_key(void) +{ + cl_git_pass(git_strmap_set(g_table, "foo", "oof")); + cl_git_pass(git_strmap_set(g_table, "bar", "rab")); + cl_git_pass(git_strmap_set(g_table, "gobble", "elbbog")); + cl_assert_equal_i(git_strmap_size(g_table), 3); + + cl_git_pass(git_strmap_set(g_table, "foo", "other")); + cl_assert_equal_i(git_strmap_size(g_table), 3); + + cl_assert_equal_s(git_strmap_get(g_table, "foo"), "other"); +} + +void test_core_strmap__iteration(void) +{ + struct { + char *key; + char *value; + int seen; + } entries[] = { + { "foo", "oof" }, + { "bar", "rab" }, + { "gobble", "elbbog" }, + }; + const char *key, *value; + size_t i, n; + + for (i = 0; i < ARRAY_SIZE(entries); i++) + cl_git_pass(git_strmap_set(g_table, entries[i].key, entries[i].value)); + + i = 0, n = 0; + while (git_strmap_iterate((void **) &value, g_table, &i, &key) == 0) { + size_t j; + + for (j = 0; j < ARRAY_SIZE(entries); j++) { + if (strcmp(entries[j].key, key)) + continue; + + cl_assert_equal_i(entries[j].seen, 0); + cl_assert_equal_s(entries[j].value, value); + entries[j].seen++; + break; + } + + n++; + } + + for (i = 0; i < ARRAY_SIZE(entries); i++) + cl_assert_equal_i(entries[i].seen, 1); + + cl_assert_equal_i(n, ARRAY_SIZE(entries)); +} + +void test_core_strmap__iterating_empty_map_stops_immediately(void) +{ + size_t i = 0; + + cl_git_fail_with(git_strmap_iterate(NULL, g_table, &i, NULL), GIT_ITEROVER); } diff --git a/tests/core/structinit.c b/tests/core/structinit.c index 8feba864e..f9da8237c 100644 --- a/tests/core/structinit.c +++ b/tests/core/structinit.c @@ -72,30 +72,35 @@ void test_core_structinit__compare(void) clar__skip(); #endif + /* apply */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_apply_options, GIT_APPLY_OPTIONS_VERSION, \ + GIT_APPLY_OPTIONS_INIT, git_apply_options_init); + /* blame */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_blame_options, GIT_BLAME_OPTIONS_VERSION, \ - GIT_BLAME_OPTIONS_INIT, git_blame_init_options); + GIT_BLAME_OPTIONS_INIT, git_blame_options_init); /* checkout */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_checkout_options, GIT_CHECKOUT_OPTIONS_VERSION, \ - GIT_CHECKOUT_OPTIONS_INIT, git_checkout_init_options); + GIT_CHECKOUT_OPTIONS_INIT, git_checkout_options_init); /* clone */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_clone_options, GIT_CLONE_OPTIONS_VERSION, \ - GIT_CLONE_OPTIONS_INIT, git_clone_init_options); + GIT_CLONE_OPTIONS_INIT, git_clone_options_init); /* diff */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_diff_options, GIT_DIFF_OPTIONS_VERSION, \ - GIT_DIFF_OPTIONS_INIT, git_diff_init_options); + GIT_DIFF_OPTIONS_INIT, git_diff_options_init); /* diff_find */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_diff_find_options, GIT_DIFF_FIND_OPTIONS_VERSION, \ - GIT_DIFF_FIND_OPTIONS_INIT, git_diff_find_init_options); + GIT_DIFF_FIND_OPTIONS_INIT, git_diff_find_options_init); /* filter */ CHECK_MACRO_FUNC_INIT_EQUAL( \ @@ -105,22 +110,22 @@ void test_core_structinit__compare(void) /* merge_file_input */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_merge_file_input, GIT_MERGE_FILE_INPUT_VERSION, \ - GIT_MERGE_FILE_INPUT_INIT, git_merge_file_init_input); + GIT_MERGE_FILE_INPUT_INIT, git_merge_file_input_init); /* merge_file */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_merge_file_options, GIT_MERGE_FILE_OPTIONS_VERSION, \ - GIT_MERGE_FILE_OPTIONS_INIT, git_merge_file_init_options); + GIT_MERGE_FILE_OPTIONS_INIT, git_merge_file_options_init); /* merge_tree */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_merge_options, GIT_MERGE_OPTIONS_VERSION, \ - GIT_MERGE_OPTIONS_INIT, git_merge_init_options); + GIT_MERGE_OPTIONS_INIT, git_merge_options_init); /* push */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_push_options, GIT_PUSH_OPTIONS_VERSION, \ - GIT_PUSH_OPTIONS_INIT, git_push_init_options); + GIT_PUSH_OPTIONS_INIT, git_push_options_init); /* remote */ CHECK_MACRO_FUNC_INIT_EQUAL( \ @@ -130,22 +135,22 @@ void test_core_structinit__compare(void) /* repository_init */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_repository_init_options, GIT_REPOSITORY_INIT_OPTIONS_VERSION, \ - GIT_REPOSITORY_INIT_OPTIONS_INIT, git_repository_init_init_options); + GIT_REPOSITORY_INIT_OPTIONS_INIT, git_repository_init_options_init); /* revert */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_revert_options, GIT_REVERT_OPTIONS_VERSION, \ - GIT_REVERT_OPTIONS_INIT, git_revert_init_options); + GIT_REVERT_OPTIONS_INIT, git_revert_options_init); /* stash apply */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_stash_apply_options, GIT_STASH_APPLY_OPTIONS_VERSION, \ - GIT_STASH_APPLY_OPTIONS_INIT, git_stash_apply_init_options); + GIT_STASH_APPLY_OPTIONS_INIT, git_stash_apply_options_init); /* status */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_status_options, GIT_STATUS_OPTIONS_VERSION, \ - GIT_STATUS_OPTIONS_INIT, git_status_init_options); + GIT_STATUS_OPTIONS_INIT, git_status_options_init); /* transport */ CHECK_MACRO_FUNC_INIT_EQUAL( \ @@ -170,14 +175,14 @@ void test_core_structinit__compare(void) /* submodule update */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_submodule_update_options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, \ - GIT_SUBMODULE_UPDATE_OPTIONS_INIT, git_submodule_update_init_options); + GIT_SUBMODULE_UPDATE_OPTIONS_INIT, git_submodule_update_options_init); /* submodule update */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_proxy_options, GIT_PROXY_OPTIONS_VERSION, \ - GIT_PROXY_OPTIONS_INIT, git_proxy_init_options); + GIT_PROXY_OPTIONS_INIT, git_proxy_options_init); CHECK_MACRO_FUNC_INIT_EQUAL( \ git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_VERSION, \ - GIT_DIFF_PATCHID_OPTIONS_INIT, git_diff_patchid_init_options); + GIT_DIFF_PATCHID_OPTIONS_INIT, git_diff_patchid_options_init); } diff --git a/tests/core/wildmatch.c b/tests/core/wildmatch.c new file mode 100644 index 000000000..e7897c6e6 --- /dev/null +++ b/tests/core/wildmatch.c @@ -0,0 +1,248 @@ +#include "clar_libgit2.h" + +#include "wildmatch.h" + +#define assert_matches(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch) \ + assert_matches_(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch, __FILE__, __LINE__) + +static void assert_matches_(const char *string, const char *pattern, + char expected_wildmatch, char expected_iwildmatch, + char expected_pathmatch, char expected_ipathmatch, + const char *file, size_t line) +{ + if (wildmatch(pattern, string, WM_PATHNAME) == expected_wildmatch) + clar__fail(file, line, "Test failed (wildmatch).", string, 1); + if (wildmatch(pattern, string, WM_PATHNAME|WM_CASEFOLD) == expected_iwildmatch) + clar__fail(file, line, "Test failed (iwildmatch).", string, 1); + if (wildmatch(pattern, string, 0) == expected_pathmatch) + clar__fail(file, line, "Test failed (pathmatch).", string, 1); + if (wildmatch(pattern, string, WM_CASEFOLD) == expected_ipathmatch) + clar__fail(file, line, "Test failed (ipathmatch).", string, 1); +} + +/* + * Below testcases are imported from git.git, t3070-wildmatch,sh at tag v2.22.0. + * Note that we've only imported the direct wildcard tests, but not the matching + * tests for git-ls-files. + */ + +void test_core_wildmatch__basic_wildmatch(void) +{ + assert_matches("foo", "foo", 1, 1, 1, 1); + assert_matches("foo", "bar", 0, 0, 0, 0); + assert_matches("", "", 1, 1, 1, 1); + assert_matches("foo", "???", 1, 1, 1, 1); + assert_matches("foo", "??", 0, 0, 0, 0); + assert_matches("foo", "*", 1, 1, 1, 1); + assert_matches("foo", "f*", 1, 1, 1, 1); + assert_matches("foo", "*f", 0, 0, 0, 0); + assert_matches("foo", "*foo*", 1, 1, 1, 1); + assert_matches("foobar", "*ob*a*r*", 1, 1, 1, 1); + assert_matches("aaaaaaabababab", "*ab", 1, 1, 1, 1); + assert_matches("foo*", "foo\\*", 1, 1, 1, 1); + assert_matches("foobar", "foo\\*bar", 0, 0, 0, 0); + assert_matches("f\\oo", "f\\\\oo", 1, 1, 1, 1); + assert_matches("ball", "*[al]?", 1, 1, 1, 1); + assert_matches("ten", "[ten]", 0, 0, 0, 0); + assert_matches("ten", "**[!te]", 1, 1, 1, 1); + assert_matches("ten", "**[!ten]", 0, 0, 0, 0); + assert_matches("ten", "t[a-g]n", 1, 1, 1, 1); + assert_matches("ten", "t[!a-g]n", 0, 0, 0, 0); + assert_matches("ton", "t[!a-g]n", 1, 1, 1, 1); + assert_matches("ton", "t[^a-g]n", 1, 1, 1, 1); + assert_matches("a]b", "a[]]b", 1, 1, 1, 1); + assert_matches("a-b", "a[]-]b", 1, 1, 1, 1); + assert_matches("a]b", "a[]-]b", 1, 1, 1, 1); + assert_matches("aab", "a[]-]b", 0, 0, 0, 0); + assert_matches("aab", "a[]a-]b", 1, 1, 1, 1); + assert_matches("]", "]", 1, 1, 1, 1); +} + +void test_core_wildmatch__slash_matching_features(void) +{ + assert_matches("foo/baz/bar", "foo*bar", 0, 0, 1, 1); + assert_matches("foo/baz/bar", "foo**bar", 0, 0, 1, 1); + assert_matches("foobazbar", "foo**bar", 1, 1, 1, 1); + assert_matches("foo/baz/bar", "foo/**/bar", 1, 1, 1, 1); + assert_matches("foo/baz/bar", "foo/**/**/bar", 1, 1, 0, 0); + assert_matches("foo/b/a/z/bar", "foo/**/bar", 1, 1, 1, 1); + assert_matches("foo/b/a/z/bar", "foo/**/**/bar", 1, 1, 1, 1); + assert_matches("foo/bar", "foo/**/bar", 1, 1, 0, 0); + assert_matches("foo/bar", "foo/**/**/bar", 1, 1, 0, 0); + assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1); + assert_matches("foo/bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 0, 0, 1, 1); + assert_matches("foo-bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 1, 1, 1, 1); + assert_matches("foo", "**/foo", 1, 1, 0, 0); + assert_matches("XXX/foo", "**/foo", 1, 1, 1, 1); + assert_matches("bar/baz/foo", "**/foo", 1, 1, 1, 1); + assert_matches("bar/baz/foo", "*/foo", 0, 0, 1, 1); + assert_matches("foo/bar/baz", "**/bar*", 0, 0, 1, 1); + assert_matches("deep/foo/bar/baz", "**/bar/*", 1, 1, 1, 1); + assert_matches("deep/foo/bar/baz/", "**/bar/*", 0, 0, 1, 1); + assert_matches("deep/foo/bar/baz/", "**/bar/**", 1, 1, 1, 1); + assert_matches("deep/foo/bar", "**/bar/*", 0, 0, 0, 0); + assert_matches("deep/foo/bar/", "**/bar/**", 1, 1, 1, 1); + assert_matches("foo/bar/baz", "**/bar**", 0, 0, 1, 1); + assert_matches("foo/bar/baz/x", "*/bar/**", 1, 1, 1, 1); + assert_matches("deep/foo/bar/baz/x", "*/bar/**", 0, 0, 1, 1); + assert_matches("deep/foo/bar/baz/x", "**/bar/*/*", 1, 1, 1, 1); +} + +void test_core_wildmatch__various_additional(void) +{ + assert_matches("acrt", "a[c-c]st", 0, 0, 0, 0); + assert_matches("acrt", "a[c-c]rt", 1, 1, 1, 1); + assert_matches("]", "[!]-]", 0, 0, 0, 0); + assert_matches("a", "[!]-]", 1, 1, 1, 1); + assert_matches("", "\\", 0, 0, 0, 0); + assert_matches("\\", "\\", 0, 0, 0, 0); + assert_matches("XXX/\\", "*/\\", 0, 0, 0, 0); + assert_matches("XXX/\\", "*/\\\\", 1, 1, 1, 1); + assert_matches("foo", "foo", 1, 1, 1, 1); + assert_matches("@foo", "@foo", 1, 1, 1, 1); + assert_matches("foo", "@foo", 0, 0, 0, 0); + assert_matches("[ab]", "\\[ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[[]ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[[:]ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[[::]ab]", 0, 0, 0, 0); + assert_matches("[ab]", "[[:digit]ab]", 1, 1, 1, 1); + assert_matches("[ab]", "[\\[:]ab]", 1, 1, 1, 1); + assert_matches("?a?b", "\\??\\?b", 1, 1, 1, 1); + assert_matches("abc", "\\a\\b\\c", 1, 1, 1, 1); + assert_matches("foo", "", 0, 0, 0, 0); + assert_matches("foo/bar/baz/to", "**/t[o]", 1, 1, 1, 1); +} + +void test_core_wildmatch__character_classes(void) +{ + assert_matches("a1B", "[[:alpha:]][[:digit:]][[:upper:]]", 1, 1, 1, 1); + assert_matches("a", "[[:digit:][:upper:][:space:]]", 0, 1, 0, 1); + assert_matches("A", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); + assert_matches("1", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); + assert_matches("1", "[[:digit:][:upper:][:spaci:]]", 0, 0, 0, 0); + assert_matches(" ", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1); + assert_matches(".", "[[:digit:][:upper:][:space:]]", 0, 0, 0, 0); + assert_matches(".", "[[:digit:][:punct:][:space:]]", 1, 1, 1, 1); + assert_matches("5", "[[:xdigit:]]", 1, 1, 1, 1); + assert_matches("f", "[[:xdigit:]]", 1, 1, 1, 1); + assert_matches("D", "[[:xdigit:]]", 1, 1, 1, 1); + assert_matches("_", "[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1); + assert_matches(".", "[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1); + assert_matches("5", "[a-c[:digit:]x-z]", 1, 1, 1, 1); + assert_matches("b", "[a-c[:digit:]x-z]", 1, 1, 1, 1); + assert_matches("y", "[a-c[:digit:]x-z]", 1, 1, 1, 1); + assert_matches("q", "[a-c[:digit:]x-z]", 0, 0, 0, 0); +} + +void test_core_wildmatch__additional_with_malformed(void) +{ + assert_matches("]", "[\\\\-^]", 1, 1, 1, 1); + assert_matches("[", "[\\\\-^]", 0, 0, 0, 0); + assert_matches("-", "[\\-_]", 1, 1, 1, 1); + assert_matches("]", "[\\]]", 1, 1, 1, 1); + assert_matches("\\]", "[\\]]", 0, 0, 0, 0); + assert_matches("\\", "[\\]]", 0, 0, 0, 0); + assert_matches("ab", "a[]b", 0, 0, 0, 0); + assert_matches("a[]b", "a[]b", 0, 0, 0, 0); + assert_matches("ab[", "ab[", 0, 0, 0, 0); + assert_matches("ab", "[!", 0, 0, 0, 0); + assert_matches("ab", "[-", 0, 0, 0, 0); + assert_matches("-", "[-]", 1, 1, 1, 1); + assert_matches("-", "[a-", 0, 0, 0, 0); + assert_matches("-", "[!a-", 0, 0, 0, 0); + assert_matches("-", "[--A]", 1, 1, 1, 1); + assert_matches("5", "[--A]", 1, 1, 1, 1); + assert_matches(" ", "[ --]", 1, 1, 1, 1); + assert_matches("$", "[ --]", 1, 1, 1, 1); + assert_matches("-", "[ --]", 1, 1, 1, 1); + assert_matches("0", "[ --]", 0, 0, 0, 0); + assert_matches("-", "[---]", 1, 1, 1, 1); + assert_matches("-", "[------]", 1, 1, 1, 1); + assert_matches("j", "[a-e-n]", 0, 0, 0, 0); + assert_matches("-", "[a-e-n]", 1, 1, 1, 1); + assert_matches("a", "[!------]", 1, 1, 1, 1); + assert_matches("[", "[]-a]", 0, 0, 0, 0); + assert_matches("^", "[]-a]", 1, 1, 1, 1); + assert_matches("^", "[!]-a]", 0, 0, 0, 0); + assert_matches("[", "[!]-a]", 1, 1, 1, 1); + assert_matches("^", "[a^bc]", 1, 1, 1, 1); + assert_matches("-b]", "[a-]b]", 1, 1, 1, 1); + assert_matches("\\", "[\\]", 0, 0, 0, 0); + assert_matches("\\", "[\\\\]", 1, 1, 1, 1); + assert_matches("\\", "[!\\\\]", 0, 0, 0, 0); + assert_matches("G", "[A-\\\\]", 1, 1, 1, 1); + assert_matches("aaabbb", "b*a", 0, 0, 0, 0); + assert_matches("aabcaa", "*ba*", 0, 0, 0, 0); + assert_matches(",", "[,]", 1, 1, 1, 1); + assert_matches(",", "[\\\\,]", 1, 1, 1, 1); + assert_matches("\\", "[\\\\,]", 1, 1, 1, 1); + assert_matches("-", "[,-.]", 1, 1, 1, 1); + assert_matches("+", "[,-.]", 0, 0, 0, 0); + assert_matches("-.]", "[,-.]", 0, 0, 0, 0); + assert_matches("2", "[\\1-\\3]", 1, 1, 1, 1); + assert_matches("3", "[\\1-\\3]", 1, 1, 1, 1); + assert_matches("4", "[\\1-\\3]", 0, 0, 0, 0); + assert_matches("\\", "[[-\\]]", 1, 1, 1, 1); + assert_matches("[", "[[-\\]]", 1, 1, 1, 1); + assert_matches("]", "[[-\\]]", 1, 1, 1, 1); + assert_matches("-", "[[-\\]]", 0, 0, 0, 0); +} + +void test_core_wildmatch__recursion(void) +{ + assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 1, 1, 1, 1); + assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0); + assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0); + assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 1, 1, 1, 1); + assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 0, 0, 0, 0); + assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt", "**/*a*b*g*n*t", 1, 1, 1, 1); + assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz", "**/*a*b*g*n*t", 0, 0, 0, 0); + assert_matches("foo", "*/*/*", 0, 0, 0, 0); + assert_matches("foo/bar", "*/*/*", 0, 0, 0, 0); + assert_matches("foo/bba/arr", "*/*/*", 1, 1, 1, 1); + assert_matches("foo/bb/aa/rr", "*/*/*", 0, 0, 1, 1); + assert_matches("foo/bb/aa/rr", "**/**/**", 1, 1, 1, 1); + assert_matches("abcXdefXghi", "*X*i", 1, 1, 1, 1); + assert_matches("ab/cXd/efXg/hi", "*X*i", 0, 0, 1, 1); + assert_matches("ab/cXd/efXg/hi", "*/*X*/*/*i", 1, 1, 1, 1); + assert_matches("ab/cXd/efXg/hi", "**/*X*/**/*i", 1, 1, 1, 1); +} + +void test_core_wildmatch__pathmatch(void) +{ + assert_matches("foo", "fo", 0, 0, 0, 0); + assert_matches("foo/bar", "foo/bar", 1, 1, 1, 1); + assert_matches("foo/bar", "foo/*", 1, 1, 1, 1); + assert_matches("foo/bba/arr", "foo/*", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/**", 1, 1, 1, 1); + assert_matches("foo/bba/arr", "foo*", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo**", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/*arr", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/**arr", 0, 0, 1, 1); + assert_matches("foo/bba/arr", "foo/*z", 0, 0, 0, 0); + assert_matches("foo/bba/arr", "foo/**z", 0, 0, 0, 0); + assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1); + assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1); + assert_matches("ab/cXd/efXg/hi", "*Xg*i", 0, 0, 1, 1); +} + +void test_core_wildmatch__case_sensitivity(void) +{ + assert_matches("a", "[A-Z]", 0, 1, 0, 1); + assert_matches("A", "[A-Z]", 1, 1, 1, 1); + assert_matches("A", "[a-z]", 0, 1, 0, 1); + assert_matches("a", "[a-z]", 1, 1, 1, 1); + assert_matches("a", "[[:upper:]]", 0, 1, 0, 1); + assert_matches("A", "[[:upper:]]", 1, 1, 1, 1); + assert_matches("A", "[[:lower:]]", 0, 1, 0, 1); + assert_matches("a", "[[:lower:]]", 1, 1, 1, 1); + assert_matches("A", "[B-Za]", 0, 1, 0, 1); + assert_matches("a", "[B-Za]", 1, 1, 1, 1); + assert_matches("A", "[B-a]", 0, 1, 0, 1); + assert_matches("a", "[B-a]", 1, 1, 1, 1); + assert_matches("z", "[Z-y]", 0, 1, 0, 1); + assert_matches("Z", "[Z-y]", 1, 1, 1, 1); +} diff --git a/tests/describe/describe_helpers.c b/tests/describe/describe_helpers.c index 2e6ad539e..80217dcf0 100644 --- a/tests/describe/describe_helpers.c +++ b/tests/describe/describe_helpers.c @@ -1,5 +1,7 @@ #include "describe_helpers.h" +#include "wildmatch.h" + void assert_describe( const char *expected_output, const char *revparse_spec, @@ -16,7 +18,7 @@ void assert_describe( cl_git_pass(git_describe_commit(&result, object, opts)); cl_git_pass(git_describe_format(&label, result, fmt_opts)); - cl_must_pass(p_fnmatch(expected_output, git_buf_cstr(&label), 0)); + cl_must_pass(wildmatch(expected_output, git_buf_cstr(&label), 0)); git_describe_result_free(result); git_object_free(object); @@ -35,7 +37,7 @@ void assert_describe_workdir( cl_git_pass(git_describe_workdir(&result, repo, opts)); cl_git_pass(git_describe_format(&label, result, fmt_opts)); - cl_must_pass(p_fnmatch(expected_output, git_buf_cstr(&label), 0)); + cl_must_pass(wildmatch(expected_output, git_buf_cstr(&label), 0)); git_describe_result_free(result); git_buf_dispose(&label); diff --git a/tests/diff/blob.c b/tests/diff/blob.c index 37898adcf..50edf6bc0 100644 --- a/tests/diff/blob.c +++ b/tests/diff/blob.c @@ -39,7 +39,7 @@ void test_diff_blob__initialize(void) g_repo = cl_git_sandbox_init("attr"); - cl_git_pass(git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION)); + cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); opts.context_lines = 1; memset(&expected, 0, sizeof(expected)); @@ -315,7 +315,7 @@ void test_diff_blob__can_compare_against_null_blobs_with_patch(void) cl_assert_equal_i(GIT_DELTA_DELETED, delta->status); cl_assert_equal_oid(git_blob_id(d), &delta->old_file.id); cl_assert_equal_sz(git_blob_rawsize(d), delta->old_file.size); - cl_assert(git_oid_iszero(&delta->new_file.id)); + cl_assert(git_oid_is_zero(&delta->new_file.id)); cl_assert_equal_sz(0, delta->new_file.size); cl_assert_equal_i(1, (int)git_patch_num_hunks(p)); @@ -338,7 +338,7 @@ void test_diff_blob__can_compare_against_null_blobs_with_patch(void) delta = git_patch_get_delta(p); cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_ADDED, delta->status); - cl_assert(git_oid_iszero(&delta->old_file.id)); + cl_assert(git_oid_is_zero(&delta->old_file.id)); cl_assert_equal_sz(0, delta->old_file.size); cl_assert_equal_oid(git_blob_id(d), &delta->new_file.id); cl_assert_equal_sz(git_blob_rawsize(d), delta->new_file.size); @@ -445,9 +445,9 @@ void test_diff_blob__can_compare_identical_blobs_with_patch(void) cl_assert(delta != NULL); cl_assert_equal_i(GIT_DELTA_UNMODIFIED, delta->status); cl_assert_equal_sz(0, delta->old_file.size); - cl_assert(git_oid_iszero(&delta->old_file.id)); + cl_assert(git_oid_is_zero(&delta->old_file.id)); cl_assert_equal_sz(0, delta->new_file.size); - cl_assert(git_oid_iszero(&delta->new_file.id)); + cl_assert(git_oid_is_zero(&delta->new_file.id)); cl_assert_equal_i(0, (int)git_patch_num_hunks(p)); git_patch_free(p); @@ -520,19 +520,19 @@ void test_diff_blob__can_compare_a_binary_blob_and_a_text_blob(void) * +++ b/a0f7217 * @@ -1,6 +1,6 @@ * Here is some stuff at the start - * + * * -This should go in one hunk * +This should go in one hunk (first) - * + * * Some additional lines - * + * * @@ -8,7 +8,7 @@ Down here below the other lines - * + * * With even more at the end - * + * * -Followed by a second hunk of stuff * +Followed by a second hunk of stuff (second) - * + * * That happens down here */ void test_diff_blob__comparing_two_text_blobs_honors_interhunkcontext(void) diff --git a/tests/diff/diff_helpers.h b/tests/diff/diff_helpers.h index 520b654d3..af855ce68 100644 --- a/tests/diff/diff_helpers.h +++ b/tests/diff/diff_helpers.h @@ -1,4 +1,4 @@ -#include "fileops.h" +#include "futils.h" #include "git2/diff.h" extern git_tree *resolve_commit_oid_to_tree( diff --git a/tests/diff/parse.c b/tests/diff/parse.c index a067861de..b004d1e23 100644 --- a/tests/diff/parse.c +++ b/tests/diff/parse.c @@ -78,6 +78,36 @@ void test_diff_parse__exact_rename(void) git_diff_free(diff); } +void test_diff_parse__empty_file(void) +{ + const char *content = + "---\n" + " file | 0\n" + " 1 file changed, 0 insertions(+), 0 deletions(-)\n" + " created mode 100644 file\n" + "\n" + "diff --git a/file b/file\n" + "new file mode 100644\n" + "index 0000000..e69de29\n" + "-- \n" + "2.20.1\n"; + git_diff *diff; + + cl_git_pass(git_diff_from_buffer( + &diff, content, strlen(content))); + git_diff_free(diff); +} + +void test_diff_parse__no_extended_headers(void) +{ + const char *content = PATCH_NO_EXTENDED_HEADERS; + git_diff *diff; + + cl_git_pass(git_diff_from_buffer( + &diff, content, strlen(content))); + git_diff_free(diff); +} + void test_diff_parse__invalid_patches_fails(void) { test_parse_invalid_diff(PATCH_CORRUPT_MISSING_NEW_FILE); diff --git a/tests/diff/patch.c b/tests/diff/patch.c index bc7976fe0..7eb353627 100644 --- a/tests/diff/patch.c +++ b/tests/diff/patch.c @@ -692,7 +692,7 @@ void test_diff_patch__can_strip_bad_utf8(void) git_patch *patch; git_buf buf = GIT_BUF_INIT; - cl_git_pass(git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION)); + cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); cl_git_pass(git_patch_from_buffers(&patch, a, strlen(a), NULL, b, strlen(b), NULL, &opts)); cl_git_pass(git_patch_to_buf(&buf, patch)); diff --git a/tests/diff/patchid.c b/tests/diff/patchid.c index 75a2aa814..621a720f7 100644 --- a/tests/diff/patchid.c +++ b/tests/diff/patchid.c @@ -20,6 +20,39 @@ void test_diff_patchid__simple_commit(void) verify_patch_id(PATCH_SIMPLE_COMMIT, "06094b1948b878b7d9ff7560b4eae672a014b0ec"); } +void test_diff_patchid__deleted_file(void) +{ + verify_patch_id(PATCH_DELETE_ORIGINAL, "d18507fe189f49c028b32c8c34e1ad98dd6a1aad"); + verify_patch_id(PATCH_DELETED_FILE_2_HUNKS, "f31412498a17e6c3fbc635f2c5f9aa3ef4c1a9b7"); +} + +void test_diff_patchid__created_file(void) +{ + verify_patch_id(PATCH_ADD_ORIGINAL, "a7d39379308021465ae2ce65e338c048a3110db6"); +} + +void test_diff_patchid__binary_file(void) +{ + verify_patch_id(PATCH_ADD_BINARY_NOT_PRINTED, "2b31236b485faa30cf4dd33e4d6539829996739f"); +} + +void test_diff_patchid__renamed_file(void) +{ + verify_patch_id(PATCH_RENAME_EXACT, "4666d50cea4976f6f727448046d43461912058fd"); + verify_patch_id(PATCH_RENAME_SIMILAR, "a795087575fcb940227be524488bedd6b3d3f438"); +} + +void test_diff_patchid__modechange(void) +{ + verify_patch_id(PATCH_MODECHANGE_UNCHANGED, "dbf3423ee98375ef1c72a79fbd29a049a2bae771"); + verify_patch_id(PATCH_MODECHANGE_MODIFIED, "93aba696e1bbd2bbb73e3e3e62ed71f232137657"); +} + +void test_diff_patchid__shuffle_hunks(void) +{ + verify_patch_id(PATCH_DELETED_FILE_2_HUNKS_SHUFFLED, "f31412498a17e6c3fbc635f2c5f9aa3ef4c1a9b7"); +} + void test_diff_patchid__filename_with_spaces(void) { verify_patch_id(PATCH_APPEND_NO_NL, "f0ba05413beaef743b630e796153839462ee477a"); diff --git a/tests/diff/tree.c b/tests/diff/tree.c index a3b00ec08..dfe4d254c 100644 --- a/tests/diff/tree.c +++ b/tests/diff/tree.c @@ -9,7 +9,7 @@ static diff_expects expect; void test_diff_tree__initialize(void) { - cl_git_pass(git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION)); + cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); memset(&expect, 0, sizeof(expect)); @@ -472,7 +472,7 @@ void test_diff_tree__diff_configs(void) cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL)); - cl_git_pass(git_diff_foreach(diff, + cl_git_pass(git_diff_foreach(diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); cl_assert_equal_i(2, expect.files); @@ -524,3 +524,52 @@ void test_diff_tree__diff_configs(void) cl_assert_equal_i(7, expect.line_adds); cl_assert_equal_i(15, expect.line_dels); } + +void test_diff_tree__diff_tree_with_empty_dir_entry_succeeds(void) +{ + const char *content = "This is a blob\n"; + const git_diff_delta *delta; + git_oid empty_tree, invalid_tree, blob; + git_buf patch = GIT_BUF_INIT; + git_treebuilder *builder; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_blob_create_from_buffer(&blob, g_repo, content, strlen(content))); + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + cl_git_pass(git_treebuilder_write(&empty_tree, builder)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "empty_tree", &empty_tree, GIT_FILEMODE_TREE)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "blob", &blob, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_write(&invalid_tree, builder)); + + cl_git_pass(git_tree_lookup(&a, g_repo, &empty_tree)); + cl_git_pass(git_tree_lookup(&b, g_repo, &invalid_tree)); + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL)); + + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &expect)); + cl_assert_equal_i(1, expect.files); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, expect.hunks); + cl_assert_equal_i(1, expect.lines); + cl_assert_equal_i(0, expect.line_ctxt); + cl_assert_equal_i(1, expect.line_adds); + cl_assert_equal_i(0, expect.line_dels); + + cl_git_pass(git_diff_to_buf(&patch, diff, GIT_DIFF_FORMAT_PATCH)); + cl_assert_equal_s(patch.ptr, + "diff --git a/blob b/blob\n" + "new file mode 100644\n" + "index 0000000..bbf2e80\n" + "--- /dev/null\n" + "+++ b/blob\n" + "@@ -0,0 +1 @@\n" + "+This is a blob\n"); + + cl_assert_equal_i(git_diff_num_deltas(diff), 1); + delta = git_diff_get_delta(diff, 0); + cl_assert_equal_s(delta->new_file.path, "blob"); + + git_treebuilder_free(builder); + git_buf_dispose(&patch); +} diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index 7a045b4fe..71b2e91a7 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -2112,7 +2112,7 @@ void test_diff_workdir__symlink_changed_on_non_symlink_platform(void) g_repo = cl_git_sandbox_init("unsymlinked.git"); - cl_git_pass(git_repository__cvar(&symlinks, g_repo, GIT_CVAR_SYMLINKS)); + cl_git_pass(git_repository__configmap_lookup(&symlinks, g_repo, GIT_CONFIGMAP_SYMLINKS)); if (symlinks) cl_skip(); @@ -2160,3 +2160,46 @@ void test_diff_workdir__symlink_changed_on_non_symlink_platform(void) git_tree_free(tree); git_vector_free(&pathlist); } + +void test_diff_workdir__order(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_buf patch = GIT_BUF_INIT; + git_oid tree_oid, blob_oid; + git_treebuilder *builder; + git_tree *tree; + git_diff *diff; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + /* Build tree with a single file "abc.txt" */ + cl_git_pass(git_blob_create_from_buffer(&blob_oid, g_repo, "foo\n", 4)); + cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "abc.txt", &blob_oid, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_write(&tree_oid, builder)); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid)); + + /* Create a directory that sorts before and one that sorts after "abc.txt" */ + cl_git_mkfile("empty_standard_repo/abc.txt", "bar\n"); + cl_must_pass(p_mkdir("empty_standard_repo/abb", 0777)); + cl_must_pass(p_mkdir("empty_standard_repo/abd", 0777)); + + opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts)); + + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + cl_git_pass(git_diff_to_buf(&patch, diff, GIT_DIFF_FORMAT_PATCH)); + cl_assert_equal_s(patch.ptr, + "diff --git a/abc.txt b/abc.txt\n" + "index 257cc56..5716ca5 100644\n" + "--- a/abc.txt\n" + "+++ b/abc.txt\n" + "@@ -1 +1 @@\n" + "-foo\n" + "+bar\n"); + + git_treebuilder_free(builder); + git_buf_dispose(&patch); + git_diff_free(diff); + git_tree_free(tree); +} diff --git a/tests/fetchhead/nonetwork.c b/tests/fetchhead/nonetwork.c index 658943273..6cea6d166 100644 --- a/tests/fetchhead/nonetwork.c +++ b/tests/fetchhead/nonetwork.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "fetchhead.h" #include "fetchhead_data.h" @@ -108,7 +108,7 @@ void test_fetchhead_nonetwork__write(void) typedef struct { git_vector *fetchhead_vector; size_t idx; -} fetchhead_ref_cb_data; +} fetchhead_ref_cb_data; static int fetchhead_ref_cb(const char *name, const char *url, const git_oid *oid, unsigned int is_merge, void *payload) @@ -493,3 +493,21 @@ void test_fetchhead_nonetwork__create_with_multiple_refspecs(void) git_remote_free(remote); git_buf_dispose(&path); } + +void test_fetchhead_nonetwork__credentials_are_stripped(void) +{ + git_fetchhead_ref *ref; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, "49322bb17d3acc9146f98c97d078513228bbf3c0")); + cl_git_pass(git_fetchhead_ref_create(&ref, &oid, 0, + "refs/tags/commit_tree", "http://foo:bar@github.com/libgit2/TestGitRepository")); + cl_assert_equal_s(ref->remote_url, "http://github.com/libgit2/TestGitRepository"); + git_fetchhead_ref_free(ref); + + cl_git_pass(git_oid_fromstr(&oid, "49322bb17d3acc9146f98c97d078513228bbf3c0")); + cl_git_pass(git_fetchhead_ref_create(&ref, &oid, 0, + "refs/tags/commit_tree", "https://foo:bar@github.com/libgit2/TestGitRepository")); + cl_assert_equal_s(ref->remote_url, "https://github.com/libgit2/TestGitRepository"); + git_fetchhead_ref_free(ref); +} diff --git a/tests/filter/bare.c b/tests/filter/bare.c new file mode 100644 index 000000000..430931ee8 --- /dev/null +++ b/tests/filter/bare.c @@ -0,0 +1,134 @@ +#include "clar_libgit2.h" +#include "crlf.h" + +static git_repository *g_repo = NULL; +static git_blob_filter_options filter_opts = GIT_BLOB_FILTER_OPTIONS_INIT; + +void test_filter_bare__initialize(void) +{ + cl_fixture_sandbox("crlf.git"); + cl_git_pass(git_repository_open(&g_repo, "crlf.git")); + + filter_opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES; + filter_opts.flags |= GIT_BLOB_FILTER_ATTTRIBUTES_FROM_HEAD; +} + +void test_filter_bare__cleanup(void) +{ + git_repository_free(g_repo); + cl_fixture_cleanup("crlf.git"); +} + +void test_filter_bare__all_crlf(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "a9a2e89")); /* all-crlf */ + + cl_assert_equal_s(ALL_CRLF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &filter_opts)); + + cl_assert_equal_s(ALL_CRLF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &filter_opts)); + + /* in this case, raw content has crlf in it already */ + cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &filter_opts)); + + /* we never convert CRLF -> LF on platforms that have LF */ + cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.txt", &filter_opts)); + + /* in this case, raw content has crlf in it already */ + cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + +void test_filter_bare__from_lf(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "799770d")); /* all-lf */ + + cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &filter_opts)); + + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &filter_opts)); + + /* in this case, raw content has crlf in it already */ + cl_assert_equal_s(ALL_LF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &filter_opts)); + + /* we never convert CRLF -> LF on platforms that have LF */ + cl_assert_equal_s(ALL_LF_TEXT_AS_LF, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + +void test_filter_bare__nested_attributes(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "799770d")); /* all-lf */ + + cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "raw/file.bin", &filter_opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "raw/file.crlf", &filter_opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "raw/file.lf", &filter_opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + +void test_filter_bare__sanitizes(void) +{ + git_blob *blob; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "e69de29")); /* zero-byte */ + + cl_assert_equal_i(0, git_blob_rawsize(blob)); + cl_assert_equal_s("", git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &filter_opts)); + cl_assert_equal_sz(0, buf.size); + cl_assert_equal_s("", buf.ptr); + git_buf_dispose(&buf); + + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &filter_opts)); + cl_assert_equal_sz(0, buf.size); + cl_assert_equal_s("", buf.ptr); + git_buf_dispose(&buf); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &filter_opts)); + cl_assert_equal_sz(0, buf.size); + cl_assert_equal_s("", buf.ptr); + git_buf_dispose(&buf); + + git_blob_free(blob); +} + diff --git a/tests/filter/blob.c b/tests/filter/blob.c index 6e30e20a3..1cc33d42f 100644 --- a/tests/filter/blob.c +++ b/tests/filter/blob.c @@ -30,16 +30,16 @@ void test_filter_blob__all_crlf(void) cl_assert_equal_s(ALL_CRLF_TEXT_RAW, git_blob_rawcontent(blob)); - cl_git_pass(git_blob_filtered_content(&buf, blob, "file.bin", 1)); + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); cl_assert_equal_s(ALL_CRLF_TEXT_RAW, buf.ptr); - cl_git_pass(git_blob_filtered_content(&buf, blob, "file.crlf", 1)); + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); /* in this case, raw content has crlf in it already */ cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); - cl_git_pass(git_blob_filtered_content(&buf, blob, "file.lf", 1)); + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); /* we never convert CRLF -> LF on platforms that have LF */ cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr); @@ -48,6 +48,34 @@ void test_filter_blob__all_crlf(void) git_blob_free(blob); } +void test_filter_blob__from_lf(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "799770d")); /* all-lf */ + + cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); + + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); + + /* in this case, raw content has crlf in it already */ + cl_assert_equal_s(ALL_LF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); + + /* we never convert CRLF -> LF on platforms that have LF */ + cl_assert_equal_s(ALL_LF_TEXT_AS_LF, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + void test_filter_blob__sanitizes(void) { git_blob *blob; @@ -60,19 +88,19 @@ void test_filter_blob__sanitizes(void) cl_assert_equal_s("", git_blob_rawcontent(blob)); memset(&buf, 0, sizeof(git_buf)); - cl_git_pass(git_blob_filtered_content(&buf, blob, "file.bin", 1)); + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); cl_assert_equal_sz(0, buf.size); cl_assert_equal_s("", buf.ptr); git_buf_dispose(&buf); memset(&buf, 0, sizeof(git_buf)); - cl_git_pass(git_blob_filtered_content(&buf, blob, "file.crlf", 1)); + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); cl_assert_equal_sz(0, buf.size); cl_assert_equal_s("", buf.ptr); git_buf_dispose(&buf); memset(&buf, 0, sizeof(git_buf)); - cl_git_pass(git_blob_filtered_content(&buf, blob, "file.lf", 1)); + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); cl_assert_equal_sz(0, buf.size); cl_assert_equal_s("", buf.ptr); git_buf_dispose(&buf); @@ -87,27 +115,27 @@ void test_filter_blob__ident(void) git_buf buf = { 0 }; cl_git_mkfile("crlf/test.ident", "Some text\n$Id$\nGoes there\n"); - cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "test.ident")); + cl_git_pass(git_blob_create_from_workdir(&id, g_repo, "test.ident")); cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); cl_assert_equal_s( "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob)); git_blob_free(blob); cl_git_mkfile("crlf/test.ident", "Some text\n$Id: Any old just you want$\nGoes there\n"); - cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "test.ident")); + cl_git_pass(git_blob_create_from_workdir(&id, g_repo, "test.ident")); cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); cl_assert_equal_s( "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob)); - cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.bin", 1)); + cl_git_pass(git_blob_filter(&buf, blob, "filter.bin", NULL)); cl_assert_equal_s( "Some text\n$Id$\nGoes there\n", buf.ptr); - cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.identcrlf", 1)); + cl_git_pass(git_blob_filter(&buf, blob, "filter.identcrlf", NULL)); cl_assert_equal_s( "Some text\r\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845 $\r\nGoes there\r\n", buf.ptr); - cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.identlf", 1)); + cl_git_pass(git_blob_filter(&buf, blob, "filter.identlf", NULL)); cl_assert_equal_s( "Some text\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845 $\nGoes there\n", buf.ptr); diff --git a/tests/filter/custom.c b/tests/filter/custom.c index b9e4f7345..fe2025fc2 100644 --- a/tests/filter/custom.c +++ b/tests/filter/custom.c @@ -208,7 +208,7 @@ void test_filter_custom__order_dependency(void) & git_index_get_bypath(index, "hero.1.rev-ident", 0)->id)); cl_assert_equal_s( "\n!nuf evaH\n$dI$\ntset a si sihT", git_blob_rawcontent(blob)); - cl_git_pass(git_blob_filtered_content(&buf, blob, "hero.1.rev-ident", 0)); + cl_git_pass(git_blob_filter(&buf, blob, "hero.1.rev-ident", NULL)); /* no expansion because id was reversed at checkin and now at ident * time, reverse is not applied yet */ cl_assert_equal_s( @@ -219,7 +219,7 @@ void test_filter_custom__order_dependency(void) & git_index_get_bypath(index, "hero.2.rev-ident", 0)->id)); cl_assert_equal_s( "\n!yzarC\n$Id$\ntset rehtonA", git_blob_rawcontent(blob)); - cl_git_pass(git_blob_filtered_content(&buf, blob, "hero.2.rev-ident", 0)); + cl_git_pass(git_blob_filter(&buf, blob, "hero.2.rev-ident", NULL)); /* expansion because reverse was applied at checkin and at ident time, * reverse is not applied yet */ cl_assert_equal_s( diff --git a/tests/filter/ident.c b/tests/filter/ident.c index cee0cc26b..ed2e4677f 100644 --- a/tests/filter/ident.c +++ b/tests/filter/ident.c @@ -23,7 +23,7 @@ static void add_blob_and_filter( git_buf out = { 0 }; cl_git_mkfile("crlf/identtest", data); - cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "identtest")); + cl_git_pass(git_blob_create_from_workdir(&id, g_repo, "identtest")); cl_git_pass(git_blob_lookup(&blob, g_repo, &id)); cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob)); diff --git a/tests/filter/systemattrs.c b/tests/filter/systemattrs.c new file mode 100644 index 000000000..4996b3b1e --- /dev/null +++ b/tests/filter/systemattrs.c @@ -0,0 +1,81 @@ +#include "clar_libgit2.h" +#include "crlf.h" +#include "path.h" + +static git_repository *g_repo = NULL; +static git_buf system_attr_path = GIT_BUF_INIT; + +void test_filter_systemattrs__initialize(void) +{ + g_repo = cl_git_sandbox_init("crlf"); + cl_must_pass(p_unlink("crlf/.gitattributes")); + + cl_git_pass(git_libgit2_opts( + GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, &system_attr_path)); + cl_git_pass(git_buf_joinpath(&system_attr_path, + system_attr_path.ptr, "gitattributes")); + + cl_git_mkfile(system_attr_path.ptr, + "*.txt text\n" + "*.bin binary\n" + "*.crlf text eol=crlf\n" + "*.lf text eol=lf\n"); +} + +void test_filter_systemattrs__cleanup(void) +{ + cl_must_pass(p_unlink(system_attr_path.ptr)); + git_buf_dispose(&system_attr_path); + + cl_git_sandbox_cleanup(); +} + +void test_filter_systemattrs__reads_system_attributes(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "799770d")); /* all-lf */ + + cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", NULL)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", NULL)); + cl_assert_equal_s(ALL_LF_TEXT_AS_CRLF, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", NULL)); + cl_assert_equal_s(ALL_LF_TEXT_AS_LF, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} + +void test_filter_systemattrs__disables_system_attributes(void) +{ + git_blob *blob; + git_buf buf = { 0 }; + git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT; + + opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES; + + cl_git_pass(git_revparse_single( + (git_object **)&blob, g_repo, "799770d")); /* all-lf */ + + cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob)); + + cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + /* No attributes mean these are all treated literally */ + cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &opts)); + cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr); + + git_buf_dispose(&buf); + git_blob_free(blob); +} diff --git a/tests/generate.py b/tests/generate.py index 0a94d4952..9ed6edef3 100644 --- a/tests/generate.py +++ b/tests/generate.py @@ -24,8 +24,8 @@ class Module(object): def render(self): out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n" - if self.module.initialize: - out += "extern %s;\n" % self.module.initialize['declaration'] + for initializer in self.module.initializers: + out += "extern %s;\n" % initializer['declaration'] if self.module.cleanup: out += "extern %s;\n" % self.module.cleanup['declaration'] @@ -41,7 +41,19 @@ class Module(object): class InfoTemplate(Template): def render(self): - return Template( + templates = [] + + initializers = self.module.initializers + if len(initializers) == 0: + initializers = [ None ] + + for initializer in initializers: + name = self.module.clean_name() + if initializer and initializer['short_name'].startswith('initialize_'): + variant = initializer['short_name'][len('initialize_'):] + name += " (%s)" % variant.replace('_', ' ') + + template = Template( r""" { "${clean_name}", @@ -49,14 +61,17 @@ class Module(object): ${cleanup}, ${cb_ptr}, ${cb_count}, ${enabled} }""" - ).substitute( - clean_name = self.module.clean_name(), - initialize = self._render_callback(self.module.initialize), - cleanup = self._render_callback(self.module.cleanup), - cb_ptr = "_clar_cb_%s" % self.module.name, - cb_count = len(self.module.callbacks), - enabled = int(self.module.enabled) - ) + ).substitute( + clean_name = name, + initialize = self._render_callback(initializer), + cleanup = self._render_callback(self.module.cleanup), + cb_ptr = "_clar_cb_%s" % self.module.name, + cb_count = len(self.module.callbacks), + enabled = int(self.module.enabled) + ) + templates.append(template) + + return ','.join(templates) def __init__(self, name): self.name = name @@ -86,7 +101,7 @@ class Module(object): regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE) self.callbacks = [] - self.initialize = None + self.initializers = [] self.cleanup = None for (declaration, symbol, short_name) in regex.findall(contents): @@ -96,8 +111,8 @@ class Module(object): "symbol" : symbol } - if short_name == 'initialize': - self.initialize = data + if short_name.startswith('initialize'): + self.initializers.append(data) elif short_name == 'cleanup': self.cleanup = data else: @@ -195,7 +210,7 @@ class TestSuite(object): module.modified = True def suite_count(self): - return len(self.modules) + return sum(max(1, len(m.initializers)) for m in self.modules.values()) def callback_count(self): return sum(len(module.callbacks) for module in self.modules.values()) diff --git a/tests/graph/ahead_behind.c b/tests/graph/ahead_behind.c new file mode 100644 index 000000000..77d7768af --- /dev/null +++ b/tests/graph/ahead_behind.c @@ -0,0 +1,58 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; +static git_commit *commit; +static size_t ahead; +static size_t behind; + +void test_graph_ahead_behind__initialize(void) +{ + git_oid oid; + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + + cl_git_pass(git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); +} + +void test_graph_ahead_behind__cleanup(void) +{ + git_commit_free(commit); + commit = NULL; + + git_repository_free(_repo); + _repo = NULL; +} + +void test_graph_ahead_behind__returns_correct_result(void) +{ + git_oid oid; + git_oid oid2; + git_commit *other; + + cl_git_pass(git_oid_fromstr(&oid, "e90810b8df3e80c413d903f631643c716887138d")); + cl_git_pass(git_oid_fromstr(&oid2, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &oid, &oid2)); + cl_assert_equal_sz(2, ahead); + cl_assert_equal_sz(6, behind); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(commit), git_commit_id(commit))); + cl_assert_equal_sz(ahead, behind); + cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 1)); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(commit), git_commit_id(other))); + cl_assert_equal_sz(ahead, behind + 2); + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(other), git_commit_id(commit))); + cl_assert_equal_sz(ahead + 2, behind); + + git_commit_free(other); + + cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 3)); + + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(commit), git_commit_id(other))); + cl_assert_equal_sz(ahead, behind + 4); + cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, git_commit_id(other), git_commit_id(commit))); + cl_assert_equal_sz(ahead + 4, behind); + + git_commit_free(other); +} diff --git a/tests/attr/ignore.c b/tests/ignore/path.c similarity index 68% rename from tests/attr/ignore.c rename to tests/ignore/path.c index 1bf06fc1f..e23ac7712 100644 --- a/tests/attr/ignore.c +++ b/tests/ignore/path.c @@ -1,16 +1,16 @@ #include "clar_libgit2.h" #include "posix.h" #include "path.h" -#include "fileops.h" +#include "futils.h" static git_repository *g_repo = NULL; -void test_attr_ignore__initialize(void) +void test_ignore_path__initialize(void) { g_repo = cl_git_sandbox_init("attr"); } -void test_attr_ignore__cleanup(void) +void test_ignore_path__cleanup(void) { cl_git_sandbox_cleanup(); g_repo = NULL; @@ -31,7 +31,7 @@ static void assert_is_ignored_( #define assert_is_ignored(expected, filepath) \ assert_is_ignored_(expected, filepath, __FILE__, __LINE__) -void test_attr_ignore__honor_temporary_rules(void) +void test_ignore_path__honor_temporary_rules(void) { cl_git_rewritefile("attr/.gitignore", "/NewFolder\n/NewFolder/NewFolder"); @@ -41,7 +41,7 @@ void test_attr_ignore__honor_temporary_rules(void) assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); } -void test_attr_ignore__allow_root(void) +void test_ignore_path__allow_root(void) { cl_git_rewritefile("attr/.gitignore", "/"); @@ -51,7 +51,7 @@ void test_attr_ignore__allow_root(void) assert_is_ignored(false, "NewFolder/NewFolder/File.txt"); } -void test_attr_ignore__ignore_space(void) +void test_ignore_path__ignore_space(void) { cl_git_rewritefile("attr/.gitignore", "/\n\n/NewFolder \n/NewFolder/NewFolder"); @@ -61,7 +61,53 @@ void test_attr_ignore__ignore_space(void) assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); } -void test_attr_ignore__ignore_dir(void) +void test_ignore_path__intermittent_space(void) +{ + cl_git_rewritefile("attr/.gitignore", "foo bar\n"); + + assert_is_ignored(false, "foo"); + assert_is_ignored(false, "bar"); + assert_is_ignored(true, "foo bar"); +} + +void test_ignore_path__trailing_space(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "foo \n" + "bar \n" + ); + + assert_is_ignored(true, "foo"); + assert_is_ignored(false, "foo "); + assert_is_ignored(true, "bar"); + assert_is_ignored(false, "bar "); + assert_is_ignored(false, "bar "); +} + +void test_ignore_path__escaped_trailing_spaces(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "foo\\ \n" + "bar\\ \\ \n" + "baz \\ \n" + "qux\\ \n" + ); + + assert_is_ignored(false, "foo"); + assert_is_ignored(true, "foo "); + assert_is_ignored(false, "bar"); + assert_is_ignored(false, "bar "); + assert_is_ignored(true, "bar "); + assert_is_ignored(true, "baz "); + assert_is_ignored(false, "baz "); + assert_is_ignored(true, "qux "); + assert_is_ignored(false, "qux"); + assert_is_ignored(false, "qux "); +} + +void test_ignore_path__ignore_dir(void) { cl_git_rewritefile("attr/.gitignore", "dir/\n"); @@ -69,7 +115,7 @@ void test_attr_ignore__ignore_dir(void) assert_is_ignored(true, "dir/file"); } -void test_attr_ignore__ignore_dir_with_trailing_space(void) +void test_ignore_path__ignore_dir_with_trailing_space(void) { cl_git_rewritefile("attr/.gitignore", "dir/ \n"); @@ -77,7 +123,7 @@ void test_attr_ignore__ignore_dir_with_trailing_space(void) assert_is_ignored(true, "dir/file"); } -void test_attr_ignore__ignore_root(void) +void test_ignore_path__ignore_root(void) { cl_git_rewritefile("attr/.gitignore", "/\n\n/NewFolder\n/NewFolder/NewFolder"); @@ -87,7 +133,7 @@ void test_attr_ignore__ignore_root(void) assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); } -void test_attr_ignore__full_paths(void) +void test_ignore_path__full_paths(void) { cl_git_rewritefile("attr/.gitignore", "Folder/*/Contained"); @@ -107,7 +153,7 @@ void test_attr_ignore__full_paths(void) assert_is_ignored(false, "Folder/Middle/More/More/Contained/Not/Happy/Child"); } -void test_attr_ignore__more_starstar_cases(void) +void test_ignore_path__more_starstar_cases(void) { cl_must_pass(p_unlink("attr/.gitignore")); cl_git_mkfile( @@ -125,7 +171,7 @@ void test_attr_ignore__more_starstar_cases(void) assert_is_ignored(false, "sub/sub2/aaa.html"); } -void test_attr_ignore__leading_stars(void) +void test_ignore_path__leading_stars(void) { cl_git_rewritefile( "attr/.gitignore", @@ -158,7 +204,7 @@ void test_attr_ignore__leading_stars(void) assert_is_ignored(false, "dir1/kid2/file"); } -void test_attr_ignore__globs_and_path_delimiters(void) +void test_ignore_path__globs_and_path_delimiters(void) { cl_git_rewritefile("attr/.gitignore", "foo/bar/**"); assert_is_ignored(true, "foo/bar/baz"); @@ -184,10 +230,32 @@ void test_attr_ignore__globs_and_path_delimiters(void) assert_is_ignored(false, "_test/foo/bar/code/file"); } -void test_attr_ignore__skip_gitignore_directory(void) +void test_ignore_path__globs_without_star(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "*.foo\n" + "**.bar\n" + ); + + assert_is_ignored(true, ".foo"); + assert_is_ignored(true, "xyz.foo"); + assert_is_ignored(true, ".bar"); + assert_is_ignored(true, "x.bar"); + assert_is_ignored(true, "xyz.bar"); + + assert_is_ignored(true, "test/.foo"); + assert_is_ignored(true, "test/x.foo"); + assert_is_ignored(true, "test/xyz.foo"); + assert_is_ignored(true, "test/.bar"); + assert_is_ignored(true, "test/x.bar"); + assert_is_ignored(true, "test/xyz.bar"); +} + +void test_ignore_path__skip_gitignore_directory(void) { cl_git_rewritefile("attr/.git/info/exclude", "/NewFolder\n/NewFolder/NewFolder"); - p_unlink("attr/.gitignore"); + cl_must_pass(p_unlink("attr/.gitignore")); cl_assert(!git_path_exists("attr/.gitignore")); p_mkdir("attr/.gitignore", 0777); cl_git_mkfile("attr/.gitignore/garbage.txt", "new_file\n"); @@ -198,14 +266,13 @@ void test_attr_ignore__skip_gitignore_directory(void) assert_is_ignored(true, "NewFolder/NewFolder/File.txt"); } -void test_attr_ignore__subdirectory_gitignore(void) +void test_ignore_path__subdirectory_gitignore(void) { - p_unlink("attr/.gitignore"); + cl_must_pass(p_unlink("attr/.gitignore")); cl_assert(!git_path_exists("attr/.gitignore")); cl_git_mkfile( "attr/.gitignore", "file1\n"); - p_mkdir("attr/dir", 0777); cl_git_mkfile( "attr/dir/.gitignore", "file2/\n"); @@ -216,7 +283,7 @@ void test_attr_ignore__subdirectory_gitignore(void) assert_is_ignored(false, "dir/file3"); } -void test_attr_ignore__expand_tilde_to_homedir(void) +void test_ignore_path__expand_tilde_to_homedir(void) { git_config *cfg; @@ -246,7 +313,7 @@ void test_attr_ignore__expand_tilde_to_homedir(void) /* Ensure that the .gitignore in the subdirectory only affects * items in the subdirectory. */ -void test_attr_ignore__gitignore_in_subdir(void) +void test_ignore_path__gitignore_in_subdir(void) { cl_git_rmfile("attr/.gitignore"); @@ -274,7 +341,7 @@ void test_attr_ignore__gitignore_in_subdir(void) } /* Ensure that files do not match folder cases */ -void test_attr_ignore__dont_ignore_files_for_folder(void) +void test_ignore_path__dont_ignore_files_for_folder(void) { cl_git_rmfile("attr/.gitignore"); @@ -305,7 +372,7 @@ void test_attr_ignore__dont_ignore_files_for_folder(void) assert_is_ignored(false, "dir/TeSt"); } -void test_attr_ignore__symlink_to_outside(void) +void test_ignore_path__symlink_to_outside(void) { #ifdef GIT_WIN32 cl_skip(); @@ -318,7 +385,7 @@ void test_attr_ignore__symlink_to_outside(void) assert_is_ignored(true, "lala/../symlink"); } -void test_attr_ignore__test(void) +void test_ignore_path__test(void) { cl_git_rewritefile("attr/.gitignore", "/*/\n" @@ -330,7 +397,7 @@ void test_attr_ignore__test(void) assert_is_ignored(true, "bin/foo"); } -void test_attr_ignore__unignore_dir_succeeds(void) +void test_ignore_path__unignore_dir_succeeds(void) { cl_git_rewritefile("attr/.gitignore", "*.c\n" @@ -339,7 +406,7 @@ void test_attr_ignore__unignore_dir_succeeds(void) assert_is_ignored(true, "src/foo/foo.c"); } -void test_attr_ignore__case_insensitive_unignores_previous_rule(void) +void test_ignore_path__case_insensitive_unignores_previous_rule(void) { git_config *cfg; @@ -356,7 +423,7 @@ void test_attr_ignore__case_insensitive_unignores_previous_rule(void) assert_is_ignored(false, "case/file"); } -void test_attr_ignore__case_sensitive_unignore_does_nothing(void) +void test_ignore_path__case_sensitive_unignore_does_nothing(void) { git_config *cfg; @@ -373,7 +440,7 @@ void test_attr_ignore__case_sensitive_unignore_does_nothing(void) assert_is_ignored(true, "case/file"); } -void test_attr_ignore__ignored_subdirfiles_with_subdir_rule(void) +void test_ignore_path__ignored_subdirfiles_with_subdir_rule(void) { cl_git_rewritefile( "attr/.gitignore", @@ -385,7 +452,7 @@ void test_attr_ignore__ignored_subdirfiles_with_subdir_rule(void) assert_is_ignored(true, "dir/sub1/sub2"); } -void test_attr_ignore__ignored_subdirfiles_with_negations(void) +void test_ignore_path__ignored_subdirfiles_with_negations(void) { cl_git_rewritefile( "attr/.gitignore", @@ -397,7 +464,7 @@ void test_attr_ignore__ignored_subdirfiles_with_negations(void) assert_is_ignored(true, "dir/sub1/c.test"); } -void test_attr_ignore__negative_directory_rules_only_match_directories(void) +void test_ignore_path__negative_directory_rules_only_match_directories(void) { cl_git_rewritefile( "attr/.gitignore", @@ -413,3 +480,97 @@ void test_attr_ignore__negative_directory_rules_only_match_directories(void) assert_is_ignored(false, "src/A.keep"); assert_is_ignored(false, ".gitignore"); } + +void test_ignore_path__escaped_character(void) +{ + cl_git_rewritefile("attr/.gitignore", "\\c\n"); + assert_is_ignored(true, "c"); + assert_is_ignored(false, "\\c"); +} + +void test_ignore_path__escaped_newline(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "\\\nnewline\n" + ); + + assert_is_ignored(true, "\nnewline"); +} + +void test_ignore_path__escaped_glob(void) +{ + cl_git_rewritefile("attr/.gitignore", "\\*\n"); + assert_is_ignored(true, "*"); + assert_is_ignored(false, "foo"); +} + +void test_ignore_path__escaped_comments(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "#foo\n" + "\\#bar\n" + "\\##baz\n" + "\\#\\\\#qux\n" + ); + + assert_is_ignored(false, "#foo"); + assert_is_ignored(true, "#bar"); + assert_is_ignored(false, "\\#bar"); + assert_is_ignored(true, "##baz"); + assert_is_ignored(false, "\\##baz"); + assert_is_ignored(true, "#\\#qux"); + assert_is_ignored(false, "##qux"); + assert_is_ignored(false, "\\##qux"); +} + +void test_ignore_path__escaped_slash(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "\\\\\n" + "\\\\preceding\n" + "inter\\\\mittent\n" + "trailing\\\\\n" + ); + +#ifndef GIT_WIN32 + assert_is_ignored(true, "\\"); + assert_is_ignored(true, "\\preceding"); +#endif + assert_is_ignored(true, "inter\\mittent"); + assert_is_ignored(true, "trailing\\"); +} + +void test_ignore_path__escaped_space(void) +{ + cl_git_rewritefile( + "attr/.gitignore", + "foo\\\\ \n" + "bar\\\\\\ \n"); + assert_is_ignored(true, "foo\\"); + assert_is_ignored(false, "foo\\ "); + assert_is_ignored(false, "foo\\\\ "); + assert_is_ignored(false, "foo\\\\"); + assert_is_ignored(true, "bar\\ "); + assert_is_ignored(false, "bar\\\\"); + assert_is_ignored(false, "bar\\\\ "); + assert_is_ignored(false, "bar\\\\\\"); + assert_is_ignored(false, "bar\\\\\\ "); +} + +void test_ignore_path__invalid_pattern(void) +{ + cl_git_rewritefile("attr/.gitignore", "["); + assert_is_ignored(false, "[f"); + assert_is_ignored(false, "f"); +} + +void test_ignore_path__negative_prefix_rule(void) +{ + cl_git_rewritefile("attr/.gitignore", "ff*\n!f\n"); + assert_is_ignored(true, "fff"); + assert_is_ignored(true, "ff"); + assert_is_ignored(false, "f"); +} diff --git a/tests/status/ignore.c b/tests/ignore/status.c similarity index 85% rename from tests/status/ignore.c rename to tests/ignore/status.c index 496582136..5e67c1202 100644 --- a/tests/status/ignore.c +++ b/tests/ignore/status.c @@ -1,17 +1,17 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "git2/attr.h" #include "ignore.h" #include "attr.h" -#include "status_helpers.h" +#include "status/status_helpers.h" static git_repository *g_repo = NULL; -void test_status_ignore__initialize(void) +void test_ignore_status__initialize(void) { } -void test_status_ignore__cleanup(void) +void test_ignore_status__cleanup(void) { cl_git_sandbox_cleanup(); } @@ -33,7 +33,7 @@ static void assert_ignored_( #define refute_is_ignored(filepath) \ assert_ignored_(false, filepath, __FILE__, __LINE__) -void test_status_ignore__0(void) +void test_ignore_status__0(void) { struct { const char *path; @@ -75,7 +75,7 @@ void test_status_ignore__0(void) } -void test_status_ignore__1(void) +void test_ignore_status__1(void) { g_repo = cl_git_sandbox_init("attr"); @@ -90,7 +90,7 @@ void test_status_ignore__1(void) refute_is_ignored("sub/dir/"); } -void test_status_ignore__empty_repo_with_gitignore_rewrite(void) +void test_ignore_status__empty_repo_with_gitignore_rewrite(void) { status_entry_single st; @@ -134,7 +134,7 @@ void test_status_ignore__empty_repo_with_gitignore_rewrite(void) assert_is_ignored("look-ma.txt"); } -void test_status_ignore__ignore_pattern_contains_space(void) +void test_ignore_status__ignore_pattern_contains_space(void) { unsigned int flags; const mode_t mode = 0777; @@ -155,7 +155,7 @@ void test_status_ignore__ignore_pattern_contains_space(void) cl_assert(flags == GIT_STATUS_WT_NEW); } -void test_status_ignore__ignore_pattern_ignorecase(void) +void test_ignore_status__ignore_pattern_ignorecase(void) { unsigned int flags; bool ignore_case; @@ -174,7 +174,7 @@ void test_status_ignore__ignore_pattern_ignorecase(void) cl_assert(flags == ignore_case ? GIT_STATUS_IGNORED : GIT_STATUS_WT_NEW); } -void test_status_ignore__subdirectories(void) +void test_ignore_status__subdirectories(void) { status_entry_single st; @@ -250,7 +250,7 @@ static const char *test_files_1[] = { NULL }; -void test_status_ignore__subdirectories_recursion(void) +void test_ignore_status__subdirectories_recursion(void) { /* Let's try again with recursing into ignored dirs turned on */ git_status_options opts = GIT_STATUS_OPTIONS_INIT; @@ -319,7 +319,7 @@ void test_status_ignore__subdirectories_recursion(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } -void test_status_ignore__subdirectories_not_at_root(void) +void test_ignore_status__subdirectories_not_at_root(void) { git_status_options opts = GIT_STATUS_OPTIONS_INIT; status_entry_counts counts; @@ -360,7 +360,7 @@ void test_status_ignore__subdirectories_not_at_root(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } -void test_status_ignore__leading_slash_ignores(void) +void test_ignore_status__leading_slash_ignores(void) { git_status_options opts = GIT_STATUS_OPTIONS_INIT; status_entry_counts counts; @@ -413,7 +413,31 @@ void test_status_ignore__leading_slash_ignores(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } -void test_status_ignore__contained_dir_with_matching_name(void) +void test_ignore_status__multiple_leading_slash(void) +{ + static const char *test_files[] = { + "empty_standard_repo/a.test", + "empty_standard_repo/b.test", + "empty_standard_repo/c.test", + "empty_standard_repo/d.test", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "a.test\n" + "/b.test\n" + "//c.test\n" + "///d.test\n"); + + assert_is_ignored("a.test"); + assert_is_ignored("b.test"); + refute_is_ignored("c.test"); + refute_is_ignored("d.test"); +} + +void test_ignore_status__contained_dir_with_matching_name(void) { static const char *test_files[] = { "empty_standard_repo/subdir_match/aaa/subdir_match/file", @@ -453,7 +477,7 @@ void test_status_ignore__contained_dir_with_matching_name(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } -void test_status_ignore__trailing_slash_star(void) +void test_ignore_status__trailing_slash_star(void) { static const char *test_files[] = { "empty_standard_repo/file", @@ -471,7 +495,7 @@ void test_status_ignore__trailing_slash_star(void) assert_is_ignored("subdir/file"); } -void test_status_ignore__adding_internal_ignores(void) +void test_ignore_status__adding_internal_ignores(void) { g_repo = cl_git_sandbox_init("empty_standard_repo"); @@ -505,7 +529,7 @@ void test_status_ignore__adding_internal_ignores(void) assert_is_ignored("two.bar"); } -void test_status_ignore__add_internal_as_first_thing(void) +void test_ignore_status__add_internal_as_first_thing(void) { const char *add_me = "\n#################\n## Eclipse\n#################\n\n*.pydevproject\n.project\n.metadata\nbin/\ntmp/\n*.tmp\n\n"; @@ -517,7 +541,7 @@ void test_status_ignore__add_internal_as_first_thing(void) refute_is_ignored("two.bar"); } -void test_status_ignore__internal_ignores_inside_deep_paths(void) +void test_ignore_status__internal_ignores_inside_deep_paths(void) { const char *add_me = "Debug\nthis/is/deep\npatterned*/dir\n"; @@ -547,7 +571,7 @@ void test_status_ignore__internal_ignores_inside_deep_paths(void) refute_is_ignored("xthis/is/deep"); } -void test_status_ignore__automatically_ignore_bad_files(void) +void test_ignore_status__automatically_ignore_bad_files(void) { g_repo = cl_git_sandbox_init("empty_standard_repo"); @@ -571,7 +595,7 @@ void test_status_ignore__automatically_ignore_bad_files(void) refute_is_ignored("path/whatever.c"); } -void test_status_ignore__filenames_with_special_prefixes_do_not_interfere_with_status_retrieval(void) +void test_ignore_status__filenames_with_special_prefixes_do_not_interfere_with_status_retrieval(void) { status_entry_single st; char *test_cases[] = { @@ -605,7 +629,7 @@ void test_status_ignore__filenames_with_special_prefixes_do_not_interfere_with_s } } -void test_status_ignore__issue_1766_negated_ignores(void) +void test_ignore_status__issue_1766_negated_ignores(void) { unsigned int status; @@ -690,7 +714,7 @@ static void add_one_to_index(const char *file) } /* Some further broken scenarios that have been reported */ -void test_status_ignore__more_breakage(void) +void test_ignore_status__more_breakage(void) { static const char *test_files[] = { "empty_standard_repo/d1/pfx-d2/d3/d4/d5/tracked", @@ -746,7 +770,7 @@ void test_status_ignore__more_breakage(void) refute_is_ignored("d1/pfx-d2/d3/d4/untracked"); } -void test_status_ignore__negative_ignores_inside_ignores(void) +void test_ignore_status__negative_ignores_inside_ignores(void) { static const char *test_files[] = { "empty_standard_repo/top/mid/btm/tracked", @@ -798,7 +822,7 @@ void test_status_ignore__negative_ignores_inside_ignores(void) refute_is_ignored("foo/bar"); } -void test_status_ignore__negative_ignores_in_slash_star(void) +void test_ignore_status__negative_ignores_in_slash_star(void) { git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; git_status_list *list; @@ -836,7 +860,7 @@ void test_status_ignore__negative_ignores_in_slash_star(void) cl_assert(found_what_about); } -void test_status_ignore__negative_ignores_without_trailing_slash_inside_ignores(void) +void test_ignore_status__negative_ignores_without_trailing_slash_inside_ignores(void) { git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; git_status_list *list; @@ -892,7 +916,7 @@ void test_status_ignore__negative_ignores_without_trailing_slash_inside_ignores( cl_assert(found_parent_child2_file); } -void test_status_ignore__negative_directory_ignores(void) +void test_ignore_status__negative_directory_ignores(void) { static const char *test_files[] = { "empty_standard_repo/parent/child1/bar.txt", @@ -945,7 +969,7 @@ void test_status_ignore__negative_directory_ignores(void) assert_is_ignored("padded_parent/child8/bar.txt"); } -void test_status_ignore__unignore_entry_in_ignored_dir(void) +void test_ignore_status__unignore_entry_in_ignored_dir(void) { static const char *test_files[] = { "empty_standard_repo/bar.txt", @@ -967,7 +991,7 @@ void test_status_ignore__unignore_entry_in_ignored_dir(void) assert_is_ignored("nested/parent/child/bar.txt"); } -void test_status_ignore__do_not_unignore_basename_prefix(void) +void test_ignore_status__do_not_unignore_basename_prefix(void) { static const char *test_files[] = { "empty_standard_repo/foo_bar.txt", @@ -983,7 +1007,7 @@ void test_status_ignore__do_not_unignore_basename_prefix(void) assert_is_ignored("foo_bar.txt"); } -void test_status_ignore__filename_with_cr(void) +void test_ignore_status__filename_with_cr(void) { int ignored; @@ -1016,7 +1040,7 @@ void test_status_ignore__filename_with_cr(void) cl_assert_equal_i(1, ignored); } -void test_status_ignore__subdir_doesnt_match_above(void) +void test_ignore_status__subdir_doesnt_match_above(void) { int ignored, icase = 0, error; git_config *cfg; @@ -1049,7 +1073,7 @@ void test_status_ignore__subdir_doesnt_match_above(void) cl_assert_equal_i(icase, ignored); } -void test_status_ignore__negate_exact_previous(void) +void test_ignore_status__negate_exact_previous(void) { int ignored; @@ -1061,7 +1085,7 @@ void test_status_ignore__negate_exact_previous(void) cl_assert_equal_i(1, ignored); } -void test_status_ignore__negate_starstar(void) +void test_ignore_status__negate_starstar(void) { int ignored; @@ -1078,7 +1102,7 @@ void test_status_ignore__negate_starstar(void) cl_assert_equal_i(0, ignored); } -void test_status_ignore__ignore_all_toplevel_dirs_include_files(void) +void test_ignore_status__ignore_all_toplevel_dirs_include_files(void) { static const char *test_files[] = { "empty_standard_repo/README.md", @@ -1103,7 +1127,7 @@ void test_status_ignore__ignore_all_toplevel_dirs_include_files(void) refute_is_ignored("src/foo/foo.c"); } -void test_status_ignore__subdir_ignore_all_toplevel_dirs_include_files(void) +void test_ignore_status__subdir_ignore_all_toplevel_dirs_include_files(void) { static const char *test_files[] = { "empty_standard_repo/project/README.md", @@ -1128,7 +1152,7 @@ void test_status_ignore__subdir_ignore_all_toplevel_dirs_include_files(void) refute_is_ignored("project/README.md"); } -void test_status_ignore__subdir_ignore_everything_except_certain_files(void) +void test_ignore_status__subdir_ignore_everything_except_certain_files(void) { static const char *test_files[] = { "empty_standard_repo/project/README.md", @@ -1156,7 +1180,7 @@ void test_status_ignore__subdir_ignore_everything_except_certain_files(void) refute_is_ignored("project/src/foo/foo.c"); } -void test_status_ignore__deeper(void) +void test_ignore_status__deeper(void) { const char *test_files[] = { "empty_standard_repo/foo.data", @@ -1178,7 +1202,7 @@ void test_status_ignore__deeper(void) refute_is_ignored("dont_ignore/bar.data"); } -void test_status_ignore__unignored_dir_with_ignored_contents(void) +void test_ignore_status__unignored_dir_with_ignored_contents(void) { static const char *test_files[] = { "empty_standard_repo/dir/a.test", @@ -1196,7 +1220,7 @@ void test_status_ignore__unignored_dir_with_ignored_contents(void) assert_is_ignored("dir/subdir/a.test"); } -void test_status_ignore__unignored_subdirs(void) +void test_ignore_status__unignored_subdirs(void) { static const char *test_files[] = { "empty_standard_repo/dir/a.test", @@ -1213,3 +1237,100 @@ void test_status_ignore__unignored_subdirs(void) assert_is_ignored("dir/a.test"); refute_is_ignored("dir/subdir/a.test"); } + +void test_ignore_status__skips_bom(void) +{ + static const char *test_files[] = { + "empty_standard_repo/a.test", + "empty_standard_repo/b.test", + "empty_standard_repo/c.test", + "empty_standard_repo/foo.txt", + "empty_standard_repo/bar.txt", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "\xEF\xBB\xBF*.test\n"); + + assert_is_ignored("a.test"); + assert_is_ignored("b.test"); + assert_is_ignored("c.test"); + refute_is_ignored("foo.txt"); + refute_is_ignored("bar.txt"); +} + +void test_ignore_status__leading_spaces_are_significant(void) +{ + static const char *test_files[] = { + "empty_standard_repo/a.test", + "empty_standard_repo/b.test", + "empty_standard_repo/c.test", + "empty_standard_repo/d.test", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + " a.test\n" + "# this is a comment\n" + "b.test\n" + "\tc.test\n" + " # not a comment\n" + "d.test\n"); + + refute_is_ignored("a.test"); + assert_is_ignored(" a.test"); + refute_is_ignored("# this is a comment"); + assert_is_ignored("b.test"); + refute_is_ignored("c.test"); + assert_is_ignored("\tc.test"); + assert_is_ignored(" # not a comment"); + assert_is_ignored("d.test"); +} + +void test_ignore_status__override_nested_wildcard_unignore(void) +{ + git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + const git_status_entry *status; + + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/dir", 0777)); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/dir/subdir", 0777)); + cl_git_mkfile("empty_standard_repo/.gitignore", "a.test\n"); + cl_git_mkfile("empty_standard_repo/dir/.gitignore", "!*.test\n"); + cl_git_mkfile("empty_standard_repo/dir/subdir/.gitignore", "a.test\n"); + cl_git_mkfile("empty_standard_repo/dir/a.test", "pong"); + cl_git_mkfile("empty_standard_repo/dir/subdir/a.test", "pong"); + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + cl_git_pass(git_status_list_new(&statuslist, repo, &opts)); + cl_assert_equal_sz(4, git_status_list_entrycount(statuslist)); + + status = git_status_byindex(statuslist, 0); + cl_assert(status != NULL); + cl_assert_equal_s(".gitignore", status->index_to_workdir->old_file.path); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); + + status = git_status_byindex(statuslist, 1); + cl_assert(status != NULL); + cl_assert_equal_s("dir/.gitignore", status->index_to_workdir->old_file.path); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); + + status = git_status_byindex(statuslist, 2); + cl_assert(status != NULL); + cl_assert_equal_s("dir/a.test", status->index_to_workdir->old_file.path); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); + + status = git_status_byindex(statuslist, 3); + cl_assert(status != NULL); + cl_assert_equal_s("dir/subdir/.gitignore", status->index_to_workdir->old_file.path); + cl_assert_equal_i(GIT_STATUS_WT_NEW, status->status); + + git_status_list_free(statuslist); +} diff --git a/tests/index/addall.c b/tests/index/addall.c index 992cd8737..c62c3cfe6 100644 --- a/tests/index/addall.c +++ b/tests/index/addall.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "../status/status_helpers.h" #include "posix.h" -#include "fileops.h" +#include "futils.h" static git_repository *g_repo = NULL; #define TEST_DIR "addall" diff --git a/tests/index/bypath.c b/tests/index/bypath.c index f911ffb51..21d3d3ed0 100644 --- a/tests/index/bypath.c +++ b/tests/index/bypath.c @@ -338,7 +338,7 @@ void test_index_bypath__add_honors_symlink(void) git_index_entry new_entry; int symlinks; - cl_git_pass(git_repository__cvar(&symlinks, g_repo, GIT_CVAR_SYMLINKS)); + cl_git_pass(git_repository__configmap_lookup(&symlinks, g_repo, GIT_CONFIGMAP_SYMLINKS)); if (symlinks) cl_skip(); diff --git a/tests/index/filemodes.c b/tests/index/filemodes.c index f2ca4564b..af1f8035e 100644 --- a/tests/index/filemodes.c +++ b/tests/index/filemodes.c @@ -164,7 +164,7 @@ static void add_entry_and_check_mode_( git_index_entry new_entry; /* If old_filename exists, we copy that to the new file, and test - * git_index_add(), otherwise create a new entry testing git_index_add_frombuffer + * git_index_add(), otherwise create a new entry testing git_index_add_from_buffer */ if (from_file) { @@ -189,7 +189,7 @@ static void add_entry_and_check_mode_( else { const char *content = "hey there\n"; - clar__assert(!git_index_add_frombuffer(index, &new_entry, content, strlen(content)), + clar__assert(!git_index_add_from_buffer(index, &new_entry, content, strlen(content)), file, line, "Cannot add index entry from buffer", NULL, 1); } @@ -207,7 +207,7 @@ void test_index_filemodes__explicit(void) git_index *index; /* These tests should run and work everywhere, as the filemode is - * given explicitly to git_index_add or git_index_add_frombuffer + * given explicitly to git_index_add or git_index_add_from_buffer */ cl_repo_set_bool(g_repo, "core.filemode", false); @@ -271,7 +271,7 @@ void test_index_filemodes__frombuffer_requires_files(void) new_entry.path = "dummy-file.txt"; new_entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_index_add_frombuffer(index, + cl_git_pass(git_index_add_from_buffer(index, &new_entry, content, strlen(content))); cl_assert((ret_entry = git_index_get_bypath(index, "dummy-file.txt", 0))); @@ -282,7 +282,7 @@ void test_index_filemodes__frombuffer_requires_files(void) new_entry.path = "dummy-file.txt"; new_entry.mode = GIT_FILEMODE_BLOB_EXECUTABLE; - cl_git_pass(git_index_add_frombuffer(index, + cl_git_pass(git_index_add_from_buffer(index, &new_entry, content, strlen(content))); cl_assert((ret_entry = git_index_get_bypath(index, "dummy-file.txt", 0))); @@ -293,7 +293,7 @@ void test_index_filemodes__frombuffer_requires_files(void) new_entry.path = "dummy-link.txt"; new_entry.mode = GIT_FILEMODE_LINK; - cl_git_pass(git_index_add_frombuffer(index, + cl_git_pass(git_index_add_from_buffer(index, &new_entry, content, strlen(content))); cl_assert((ret_entry = git_index_get_bypath(index, "dummy-link.txt", 0))); @@ -304,7 +304,7 @@ void test_index_filemodes__frombuffer_requires_files(void) new_entry.path = "invalid_mode.txt"; new_entry.mode = GIT_FILEMODE_TREE; - cl_git_fail(git_index_add_frombuffer(index, + cl_git_fail(git_index_add_from_buffer(index, &new_entry, content, strlen(content))); cl_assert_equal_p(NULL, git_index_get_bypath(index, "invalid_mode.txt", 0)); @@ -312,7 +312,7 @@ void test_index_filemodes__frombuffer_requires_files(void) new_entry.path = "invalid_mode.txt"; new_entry.mode = GIT_FILEMODE_COMMIT; - cl_git_fail(git_index_add_frombuffer(index, + cl_git_fail(git_index_add_from_buffer(index, &new_entry, content, strlen(content))); cl_assert_equal_p(NULL, git_index_get_bypath(index, "invalid_mode.txt", 0)); diff --git a/tests/index/nsec.c b/tests/index/nsec.c index dee1509e1..6edcf030a 100644 --- a/tests/index/nsec.c +++ b/tests/index/nsec.c @@ -54,7 +54,7 @@ static bool should_expect_nsecs(void) expect = try_create_file_with_nsec_timestamp(nsec_path.ptr); - p_unlink(nsec_path.ptr); + cl_must_pass(p_unlink(nsec_path.ptr)); git_buf_dispose(&nsec_path); diff --git a/tests/index/racy.c b/tests/index/racy.c index 88b37e10d..b06f55de4 100644 --- a/tests/index/racy.c +++ b/tests/index/racy.c @@ -193,7 +193,7 @@ static void setup_uptodate_files(void) /* Put 'C' into the index */ new_entry.path = "C"; new_entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_index_add_frombuffer(index, &new_entry, "hello!\n", 7)); + cl_git_pass(git_index_add_from_buffer(index, &new_entry, "hello!\n", 7)); git_index_free(index); git_buf_dispose(&path); diff --git a/tests/index/tests.c b/tests/index/tests.c index 4c8c2c568..d9d8371dd 100644 --- a/tests/index/tests.c +++ b/tests/index/tests.c @@ -13,7 +13,7 @@ static const size_t index_entry_count_2 = 1437; struct test_entry { size_t index; char path[128]; - git_off_t file_size; + off64_t file_size; git_time_t mtime; }; @@ -310,7 +310,7 @@ void test_index_tests__add_frombuffer(void) memset(&entry, 0x0, sizeof(git_index_entry)); entry.mode = GIT_FILEMODE_BLOB; entry.path = "test.txt"; - cl_git_pass(git_index_add_frombuffer(index, &entry, + cl_git_pass(git_index_add_from_buffer(index, &entry, content, strlen(content))); /* Wow... it worked! */ @@ -352,7 +352,7 @@ void test_index_tests__dirty_and_clean(void) /* Index is dirty after adding an entry */ entry.mode = GIT_FILEMODE_BLOB; entry.path = "test.txt"; - cl_git_pass(git_index_add_frombuffer(index, &entry, "Hi.\n", 4)); + cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); cl_assert(git_index_entrycount(index) == 1); cl_assert(git_index_is_dirty(index)); @@ -374,7 +374,7 @@ void test_index_tests__dirty_and_clean(void) cl_assert(!git_index_is_dirty(index)); /* Index is dirty when we do an unforced read with dirty content */ - cl_git_pass(git_index_add_frombuffer(index, &entry, "Hi.\n", 4)); + cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); cl_assert(git_index_entrycount(index) == 1); cl_assert(git_index_is_dirty(index)); @@ -402,7 +402,7 @@ void test_index_tests__dirty_fails_optionally(void) /* Index is dirty after adding an entry */ entry.mode = GIT_FILEMODE_BLOB; entry.path = "test.txt"; - cl_git_pass(git_index_add_frombuffer(index, &entry, "Hi.\n", 4)); + cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); cl_assert(git_index_is_dirty(index)); cl_git_pass(git_checkout_head(repo, NULL)); @@ -410,7 +410,7 @@ void test_index_tests__dirty_fails_optionally(void) /* Index is dirty (again) after adding an entry */ entry.mode = GIT_FILEMODE_BLOB; entry.path = "test.txt"; - cl_git_pass(git_index_add_frombuffer(index, &entry, "Hi.\n", 4)); + cl_git_pass(git_index_add_from_buffer(index, &entry, "Hi.\n", 4)); cl_assert(git_index_is_dirty(index)); cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, 1)); @@ -455,7 +455,7 @@ void test_index_tests__add_frombuffer_reset_entry(void) memset(&entry, 0x0, sizeof(git_index_entry)); entry.mode = GIT_FILEMODE_BLOB; entry.path = "test.txt"; - cl_git_pass(git_index_add_frombuffer(index, &entry, + cl_git_pass(git_index_add_from_buffer(index, &entry, content, strlen(content))); /* Wow... it worked! */ @@ -566,8 +566,7 @@ void test_index_tests__cannot_add_invalid_filename(void) { git_repository *repo; - p_mkdir("invalid", 0700); - + cl_must_pass(p_mkdir("invalid", 0700)); cl_git_pass(git_repository_init(&repo, "./invalid", 0)); cl_must_pass(p_mkdir("./invalid/subdir", 0777)); diff --git a/tests/index/version.c b/tests/index/version.c index 7ada302b5..b6c0b7918 100644 --- a/tests/index/version.c +++ b/tests/index/version.c @@ -43,6 +43,7 @@ void test_index_version__can_write_v4(void) "xz", "xyzzyx" }; + git_repository *repo; git_index_entry entry; git_index *index; size_t i; @@ -55,7 +56,7 @@ void test_index_version__can_write_v4(void) memset(&entry, 0, sizeof(entry)); entry.path = paths[i]; entry.mode = GIT_FILEMODE_BLOB; - cl_git_pass(git_index_add_frombuffer(index, &entry, paths[i], + cl_git_pass(git_index_add_from_buffer(index, &entry, paths[i], strlen(paths[i]) + 1)); } cl_assert_equal_sz(git_index_entrycount(index), ARRAY_SIZE(paths)); @@ -63,7 +64,8 @@ void test_index_version__can_write_v4(void) cl_git_pass(git_index_write(index)); git_index_free(index); - cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_repository_open(&repo, git_repository_path(g_repo))); + cl_git_pass(git_repository_index(&index, repo)); cl_assert(git_index_version(index) == 4); for (i = 0; i < ARRAY_SIZE(paths); i++) { @@ -74,6 +76,7 @@ void test_index_version__can_write_v4(void) } git_index_free(index); + git_repository_free(repo); } void test_index_version__v4_uses_path_compression(void) @@ -100,7 +103,7 @@ void test_index_version__v4_uses_path_compression(void) path[ARRAY_SIZE(path) - 3] = i; path[ARRAY_SIZE(path) - 2] = j; path[ARRAY_SIZE(path) - 1] = '\0'; - cl_git_pass(git_index_add_frombuffer(index, &entry, buf, sizeof(buf))); + cl_git_pass(git_index_add_from_buffer(index, &entry, buf, sizeof(buf))); } } diff --git a/tests/iterator/index.c b/tests/iterator/index.c index 8c7efb253..25d8c2990 100644 --- a/tests/iterator/index.c +++ b/tests/iterator/index.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "iterator.h" #include "repository.h" -#include "fileops.h" +#include "futils.h" #include "iterator_helpers.h" #include "../submodule/submodule_helpers.h" #include diff --git a/tests/iterator/iterator_helpers.c b/tests/iterator/iterator_helpers.c index 68d574126..b210dbb0c 100644 --- a/tests/iterator/iterator_helpers.c +++ b/tests/iterator/iterator_helpers.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "iterator.h" #include "repository.h" -#include "fileops.h" +#include "futils.h" #include "iterator_helpers.h" #include diff --git a/tests/iterator/tree.c b/tests/iterator/tree.c index 08df909a3..f7fb9a7ee 100644 --- a/tests/iterator/tree.c +++ b/tests/iterator/tree.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "iterator.h" #include "repository.h" -#include "fileops.h" +#include "futils.h" #include "tree.h" #include "../submodule/submodule_helpers.h" #include "../diff/diff_helpers.h" diff --git a/tests/iterator/workdir.c b/tests/iterator/workdir.c index 87ac1e5d4..547fb7d95 100644 --- a/tests/iterator/workdir.c +++ b/tests/iterator/workdir.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "iterator.h" #include "repository.h" -#include "fileops.h" +#include "futils.h" #include "../submodule/submodule_helpers.h" #include "../merge/merge_helpers.h" #include "iterator_helpers.h" @@ -100,7 +100,7 @@ static void workdir_iterator_test( void test_iterator_workdir__0(void) { - workdir_iterator_test("attr", NULL, NULL, 23, 5, NULL, "ign"); + workdir_iterator_test("attr", NULL, NULL, 24, 5, NULL, "ign"); } static const char *status_paths[] = { @@ -620,6 +620,7 @@ void test_iterator_workdir__filesystem2(void) "heads/subtrees", "heads/test", "heads/testrepo-worktree", + "symref", "tags/e90810b", "tags/foo/bar", "tags/foo/foo/bar", @@ -632,17 +633,18 @@ void test_iterator_workdir__filesystem2(void) cl_git_pass(git_iterator_for_filesystem( &i, "testrepo/.git/refs", NULL)); - expect_iterator_items(i, 16, expect_base, 16, expect_base); + expect_iterator_items(i, 17, expect_base, 17, expect_base); git_iterator_free(i); } -/* Lots of empty dirs, or nearly empty ones, make the old workdir - * iterator cry. Also, segfault. +/* + * Lots of empty dirs, or nearly empty ones, make the old workdir + * iterator cry. Also, segfault. */ void test_iterator_workdir__filesystem_gunk(void) { - git_iterator *i; git_buf parent = GIT_BUF_INIT; + git_iterator *i; int n; if (!cl_is_env_set("GITTEST_INVASIVE_SPEED")) @@ -652,19 +654,18 @@ void test_iterator_workdir__filesystem_gunk(void) for (n = 0; n < 100000; n++) { git_buf_clear(&parent); - git_buf_printf(&parent, "%s/refs/heads/foo/%d/subdir", - git_repository_path(g_repo), n); - cl_assert(!git_buf_oom(&parent)); - + cl_git_pass(git_buf_printf(&parent, "%s/refs/heads/foo/%d/subdir", git_repository_path(g_repo), n)); cl_git_pass(git_futils_mkdir(parent.ptr, 0775, GIT_MKDIR_PATH)); } cl_git_pass(git_iterator_for_filesystem(&i, "testrepo/.git/refs", NULL)); - /* should only have 16 items, since we're not asking for trees to be + /* + * Should only have 17 items, since we're not asking for trees to be * returned. the goal of this test is simply to not crash. */ - expect_iterator_items(i, 16, NULL, 15, NULL); + expect_iterator_items(i, 17, NULL, 16, NULL); + git_iterator_free(i); git_buf_dispose(&parent); } diff --git a/tests/merge/workdir/analysis.c b/tests/merge/analysis.c similarity index 67% rename from tests/merge/workdir/analysis.c rename to tests/merge/analysis.c index 27d7dba84..1432a7d11 100644 --- a/tests/merge/workdir/analysis.c +++ b/tests/merge/analysis.c @@ -1,39 +1,49 @@ +/* +NOTE: this is the implementation for both merge/trees/analysis.c and merge/workdir/analysis.c +You probably want to make changes to both files. +*/ + #include "clar_libgit2.h" #include "git2/repository.h" #include "git2/merge.h" #include "git2/annotated_commit.h" #include "git2/sys/index.h" #include "merge.h" -#include "../merge_helpers.h" +#include "merge_helpers.h" #include "refs.h" #include "posix.h" -static git_repository *repo; -static git_index *repo_index; - #define TEST_REPO_PATH "merge-resolve" -#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" -#define UPTODATE_BRANCH "master" -#define PREVIOUS_BRANCH "previous" +#define UPTODATE_BRANCH "master" +#define PREVIOUS_BRANCH "previous" -#define FASTFORWARD_BRANCH "ff_branch" -#define FASTFORWARD_ID "fd89f8cffb663ac89095a0f9764902e93ceaca6a" +#define FASTFORWARD_BRANCH "ff_branch" +#define FASTFORWARD_ID "fd89f8cffb663ac89095a0f9764902e93ceaca6a" -#define NOFASTFORWARD_BRANCH "branch" -#define NOFASTFORWARD_ID "7cb63eed597130ba4abb87b3e544b85021905520" +#define NOFASTFORWARD_BRANCH "branch" +#define NOFASTFORWARD_ID "7cb63eed597130ba4abb87b3e544b85021905520" +static git_repository *sandbox; +static git_repository *repo; -/* Fixture setup and teardown */ -void test_merge_workdir_analysis__initialize(void) +void test_merge_analysis__initialize_with_bare_repository(void) { - repo = cl_git_sandbox_init(TEST_REPO_PATH); - git_repository_index(&repo_index, repo); + sandbox = cl_git_sandbox_init(TEST_REPO_PATH); + cl_git_pass(git_repository_open_ext(&repo, git_repository_path(sandbox), + GIT_REPOSITORY_OPEN_BARE, NULL)); } -void test_merge_workdir_analysis__cleanup(void) +void test_merge_analysis__initialize_with_nonbare_repository(void) { - git_index_free(repo_index); + sandbox = cl_git_sandbox_init(TEST_REPO_PATH); + cl_git_pass(git_repository_open_ext(&repo, git_repository_workdir(sandbox), + 0, NULL)); +} + +void test_merge_analysis__cleanup(void) +{ + git_repository_free(repo); cl_git_sandbox_cleanup(); } @@ -70,7 +80,7 @@ static void analysis_from_branch( git_reference_free(their_ref); } -void test_merge_workdir_analysis__fastforward(void) +void test_merge_analysis__fastforward(void) { git_merge_analysis_t merge_analysis; git_merge_preference_t merge_pref; @@ -79,7 +89,7 @@ void test_merge_workdir_analysis__fastforward(void) cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL|GIT_MERGE_ANALYSIS_FASTFORWARD, merge_analysis); } -void test_merge_workdir_analysis__no_fastforward(void) +void test_merge_analysis__no_fastforward(void) { git_merge_analysis_t merge_analysis; git_merge_preference_t merge_pref; @@ -88,7 +98,7 @@ void test_merge_workdir_analysis__no_fastforward(void) cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis); } -void test_merge_workdir_analysis__uptodate(void) +void test_merge_analysis__uptodate(void) { git_merge_analysis_t merge_analysis; git_merge_preference_t merge_pref; @@ -97,7 +107,7 @@ void test_merge_workdir_analysis__uptodate(void) cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); } -void test_merge_workdir_analysis__uptodate_merging_prev_commit(void) +void test_merge_analysis__uptodate_merging_prev_commit(void) { git_merge_analysis_t merge_analysis; git_merge_preference_t merge_pref; @@ -106,14 +116,14 @@ void test_merge_workdir_analysis__uptodate_merging_prev_commit(void) cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); } -void test_merge_workdir_analysis__unborn(void) +void test_merge_analysis__unborn(void) { git_merge_analysis_t merge_analysis; git_merge_preference_t merge_pref; git_buf master = GIT_BUF_INIT; - git_buf_joinpath(&master, git_repository_path(repo), "refs/heads/master"); - p_unlink(git_buf_cstr(&master)); + cl_git_pass(git_buf_joinpath(&master, git_repository_path(repo), "refs/heads/master")); + cl_must_pass(p_unlink(git_buf_cstr(&master))); analysis_from_branch(&merge_analysis, &merge_pref, NULL, NOFASTFORWARD_BRANCH); cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD|GIT_MERGE_ANALYSIS_UNBORN, merge_analysis); @@ -121,37 +131,41 @@ void test_merge_workdir_analysis__unborn(void) git_buf_dispose(&master); } -void test_merge_workdir_analysis__fastforward_with_config_noff(void) +void test_merge_analysis__fastforward_with_config_noff(void) { git_config *config; git_merge_analysis_t merge_analysis; git_merge_preference_t merge_pref; - git_repository_config(&config, repo); - git_config_set_string(config, "merge.ff", "false"); + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_string(config, "merge.ff", "false")); analysis_from_branch(&merge_analysis, &merge_pref, NULL, FASTFORWARD_BRANCH); cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL|GIT_MERGE_ANALYSIS_FASTFORWARD, merge_analysis); cl_assert_equal_i(GIT_MERGE_PREFERENCE_NO_FASTFORWARD, (merge_pref & GIT_MERGE_PREFERENCE_NO_FASTFORWARD)); + + git_config_free(config); } -void test_merge_workdir_analysis__no_fastforward_with_config_ffonly(void) +void test_merge_analysis__no_fastforward_with_config_ffonly(void) { git_config *config; git_merge_analysis_t merge_analysis; git_merge_preference_t merge_pref; - git_repository_config(&config, repo); - git_config_set_string(config, "merge.ff", "only"); + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_string(config, "merge.ff", "only")); analysis_from_branch(&merge_analysis, &merge_pref, NULL, NOFASTFORWARD_BRANCH); cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis); cl_assert_equal_i(GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY, (merge_pref & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY)); + + git_config_free(config); } -void test_merge_workdir_analysis__between_uptodate_refs(void) +void test_merge_analysis__between_uptodate_refs(void) { git_merge_analysis_t merge_analysis; git_merge_preference_t merge_pref; @@ -160,7 +174,7 @@ void test_merge_workdir_analysis__between_uptodate_refs(void) cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); } -void test_merge_workdir_analysis__between_noff_refs(void) +void test_merge_analysis__between_noff_refs(void) { git_merge_analysis_t merge_analysis; git_merge_preference_t merge_pref; diff --git a/tests/merge/files.c b/tests/merge/files.c index 27c96363f..6877f9848 100644 --- a/tests/merge/files.c +++ b/tests/merge/files.c @@ -6,7 +6,7 @@ #include "merge_helpers.h" #include "conflict_data.h" #include "refs.h" -#include "fileops.h" +#include "futils.h" #include "diff_xdiff.h" #define TEST_REPO_PATH "merge-resolve" diff --git a/tests/merge/merge_helpers.c b/tests/merge/merge_helpers.c index 1b68bdccb..27f355f35 100644 --- a/tests/merge/merge_helpers.c +++ b/tests/merge/merge_helpers.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "refs.h" #include "tree.h" #include "merge_helpers.h" @@ -352,7 +352,7 @@ int merge_test_workdir(git_repository *repo, const struct merge_index_entry expe return 0; for (i = 0; i < expected_len; i++) { - git_blob_create_fromworkdir(&actual_oid, repo, expected[i].path); + git_blob_create_from_workdir(&actual_oid, repo, expected[i].path); git_oid_fromstr(&expected_oid, expected[i].oid_str); if (git_oid_cmp(&actual_oid, &expected_oid) != 0) diff --git a/tests/merge/merge_helpers.h b/tests/merge/merge_helpers.h index e407c7d13..166b4eefd 100644 --- a/tests/merge/merge_helpers.h +++ b/tests/merge/merge_helpers.h @@ -36,7 +36,7 @@ struct merge_index_conflict_data { struct merge_index_with_status ancestor; struct merge_index_with_status ours; struct merge_index_with_status theirs; - git_merge_diff_type_t change_type; + git_merge_diff_t change_type; }; int merge_trees_from_branches( diff --git a/tests/merge/trees/automerge.c b/tests/merge/trees/automerge.c index e4efba51c..dd26464fb 100644 --- a/tests/merge/trees/automerge.c +++ b/tests/merge/trees/automerge.c @@ -3,7 +3,7 @@ #include "git2/merge.h" #include "buffer.h" #include "merge.h" -#include "fileops.h" +#include "futils.h" #include "../merge_helpers.h" #include "../conflict_data.h" diff --git a/tests/merge/trees/modeconflict.c b/tests/merge/trees/modeconflict.c index e85e340b9..32866ea6d 100644 --- a/tests/merge/trees/modeconflict.c +++ b/tests/merge/trees/modeconflict.c @@ -4,7 +4,7 @@ #include "buffer.h" #include "merge.h" #include "../merge_helpers.h" -#include "fileops.h" +#include "futils.h" static git_repository *repo; diff --git a/tests/merge/trees/renames.c b/tests/merge/trees/renames.c index fbcfd2d3b..c515aaf1b 100644 --- a/tests/merge/trees/renames.c +++ b/tests/merge/trees/renames.c @@ -4,7 +4,7 @@ #include "buffer.h" #include "merge.h" #include "../merge_helpers.h" -#include "fileops.h" +#include "futils.h" static git_repository *repo; @@ -274,3 +274,80 @@ void test_merge_trees_renames__submodules(void) cl_assert(merge_test_index(index, merge_index_entries, 7)); git_index_free(index); } + +void test_merge_trees_renames__cache_recomputation(void) +{ + git_oid blob, binary, ancestor_oid, theirs_oid, ours_oid; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + git_buf path = GIT_BUF_INIT; + git_treebuilder *builder; + git_tree *ancestor_tree, *their_tree, *our_tree; + git_index *index; + size_t blob_size; + void *data; + size_t i; + + cl_git_pass(git_oid_fromstr(&blob, "a2d8d1824c68541cca94ffb90f79291eba495921")); + + /* + * Create a 50MB blob that consists of NUL bytes only. It is important + * that this blob is of a special format, most importantly it cannot + * contain more than four non-consecutive newlines or NUL bytes. This + * is because of git_hashsig's inner workings where all files with less + * than four "lines" are deemed to small. + */ + blob_size = 50 * 1024 * 1024; + cl_assert(data = git__calloc(blob_size, 1)); + cl_git_pass(git_blob_create_from_buffer(&binary, repo, data, blob_size)); + + /* + * Create the common ancestor, which has 1000 dummy blobs and the binary + * blob. The dummy blobs serve as potential rename targets for the + * dummy blob. + */ + cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); + for (i = 0; i < 1000; i++) { + cl_git_pass(git_buf_printf(&path, "%"PRIuMAX".txt", i)); + cl_git_pass(git_treebuilder_insert(NULL, builder, path.ptr, &blob, GIT_FILEMODE_BLOB)); + git_buf_clear(&path); + } + cl_git_pass(git_treebuilder_insert(NULL, builder, "original.bin", &binary, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_write(&ancestor_oid, builder)); + + /* We now the binary blob in our tree. */ + cl_git_pass(git_treebuilder_remove(builder, "original.bin")); + cl_git_pass(git_treebuilder_insert(NULL, builder, "renamed.bin", &binary, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_write(&ours_oid, builder)); + + git_treebuilder_free(builder); + + /* And move everything into a subdirectory in their tree. */ + cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "subdir", &ancestor_oid, GIT_FILEMODE_TREE)); + cl_git_pass(git_treebuilder_write(&theirs_oid, builder)); + + /* + * Now merge ancestor, ours and theirs. As `git_hashsig` refuses to + * create a hash signature for the 50MB binary file, we historically + * didn't cache the hashsig computation for it. As a result, we now + * started looking up the 50MB blob and scanning it at least 1000 + * times, which takes a long time. + * + * The number of 1000 blobs is chosen in such a way that it's + * noticeable when the bug creeps in again, as it takes around 12 + * minutes on my machine to compute the following merge. + */ + opts.target_limit = 5000; + cl_git_pass(git_tree_lookup(&ancestor_tree, repo, &ancestor_oid)); + cl_git_pass(git_tree_lookup(&their_tree, repo, &theirs_oid)); + cl_git_pass(git_tree_lookup(&our_tree, repo, &ours_oid)); + cl_git_pass(git_merge_trees(&index, repo, ancestor_tree, our_tree, their_tree, &opts)); + + git_treebuilder_free(builder); + git_buf_dispose(&path); + git_index_free(index); + git_tree_free(ancestor_tree); + git_tree_free(their_tree); + git_tree_free(our_tree); + git__free(data); +} diff --git a/tests/merge/trees/trivial.c b/tests/merge/trees/trivial.c index 4a8255624..ac4f09f80 100644 --- a/tests/merge/trees/trivial.c +++ b/tests/merge/trees/trivial.c @@ -4,7 +4,7 @@ #include "merge.h" #include "../merge_helpers.h" #include "refs.h" -#include "fileops.h" +#include "futils.h" #include "git2/sys/index.h" static git_repository *repo; diff --git a/tests/merge/trees/whitespace.c b/tests/merge/trees/whitespace.c index fdb11253b..ce7703496 100644 --- a/tests/merge/trees/whitespace.c +++ b/tests/merge/trees/whitespace.c @@ -4,7 +4,7 @@ #include "buffer.h" #include "merge.h" #include "../merge_helpers.h" -#include "fileops.h" +#include "futils.h" static git_repository *repo; diff --git a/tests/merge/workdir/renames.c b/tests/merge/workdir/renames.c index a8ee59a44..e8cd333af 100644 --- a/tests/merge/workdir/renames.c +++ b/tests/merge/workdir/renames.c @@ -4,7 +4,7 @@ #include "buffer.h" #include "merge.h" #include "../merge_helpers.h" -#include "fileops.h" +#include "futils.h" #include "refs.h" static git_repository *repo; diff --git a/tests/merge/workdir/setup.c b/tests/merge/workdir/setup.c index 3a8f9d987..ad29fcd94 100644 --- a/tests/merge/workdir/setup.c +++ b/tests/merge/workdir/setup.c @@ -3,7 +3,7 @@ #include "git2/merge.h" #include "merge.h" #include "refs.h" -#include "fileops.h" +#include "futils.h" static git_repository *repo; static git_index *repo_index; diff --git a/tests/merge/workdir/simple.c b/tests/merge/workdir/simple.c index a8d5d0be0..6b4e17492 100644 --- a/tests/merge/workdir/simple.c +++ b/tests/merge/workdir/simple.c @@ -6,7 +6,7 @@ #include "../merge_helpers.h" #include "../conflict_data.h" #include "refs.h" -#include "fileops.h" +#include "futils.h" static git_repository *repo; static git_index *repo_index; diff --git a/tests/merge/workdir/trivial.c b/tests/merge/workdir/trivial.c index 39d1ddc9b..c5bb7030e 100644 --- a/tests/merge/workdir/trivial.c +++ b/tests/merge/workdir/trivial.c @@ -5,7 +5,7 @@ #include "merge.h" #include "../merge_helpers.h" #include "refs.h" -#include "fileops.h" +#include "futils.h" static git_repository *repo; static git_index *repo_index; diff --git a/tests/network/cred.c b/tests/network/cred.c index 6994cc0c3..5e4db7599 100644 --- a/tests/network/cred.c +++ b/tests/network/cred.c @@ -4,47 +4,43 @@ void test_network_cred__stock_userpass_validates_args(void) { - git_cred_userpass_payload payload = {0}; + git_credential_userpass_payload payload = {0}; - cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, NULL)); + cl_git_fail(git_credential_userpass(NULL, NULL, NULL, 0, NULL)); payload.username = "user"; - cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, &payload)); + cl_git_fail(git_credential_userpass(NULL, NULL, NULL, 0, &payload)); payload.username = NULL; payload.username = "pass"; - cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, &payload)); + cl_git_fail(git_credential_userpass(NULL, NULL, NULL, 0, &payload)); } void test_network_cred__stock_userpass_validates_that_method_is_allowed(void) { - git_cred *cred; - git_cred_userpass_payload payload = {"user", "pass"}; + git_credential *cred; + git_credential_userpass_payload payload = {"user", "pass"}; - cl_git_fail(git_cred_userpass(&cred, NULL, NULL, 0, &payload)); - cl_git_pass(git_cred_userpass(&cred, NULL, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload)); - cred->free(cred); + cl_git_fail(git_credential_userpass(&cred, NULL, NULL, 0, &payload)); + cl_git_pass(git_credential_userpass(&cred, NULL, NULL, GIT_CREDENTIAL_USERPASS_PLAINTEXT, &payload)); + git_credential_free(cred); } void test_network_cred__stock_userpass_properly_handles_username_in_url(void) { - git_cred *cred; - git_cred_userpass_plaintext *plain; - git_cred_userpass_payload payload = {"alice", "password"}; + git_credential *cred; + git_credential_userpass_payload payload = {"alice", "password"}; - cl_git_pass(git_cred_userpass(&cred, NULL, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload)); - plain = (git_cred_userpass_plaintext*)cred; - cl_assert_equal_s(plain->username, "alice"); - cred->free(cred); + cl_git_pass(git_credential_userpass(&cred, NULL, NULL, GIT_CREDENTIAL_USERPASS_PLAINTEXT, &payload)); + cl_assert_equal_s("alice", git_credential_get_username(cred)); + git_credential_free(cred); - cl_git_pass(git_cred_userpass(&cred, NULL, "bob", GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload)); - plain = (git_cred_userpass_plaintext*)cred; - cl_assert_equal_s(plain->username, "alice"); - cred->free(cred); + cl_git_pass(git_credential_userpass(&cred, NULL, "bob", GIT_CREDENTIAL_USERPASS_PLAINTEXT, &payload)); + cl_assert_equal_s("alice", git_credential_get_username(cred)); + git_credential_free(cred); payload.username = NULL; - cl_git_pass(git_cred_userpass(&cred, NULL, "bob", GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload)); - plain = (git_cred_userpass_plaintext*)cred; - cl_assert_equal_s(plain->username, "bob"); - cred->free(cred); + cl_git_pass(git_credential_userpass(&cred, NULL, "bob", GIT_CREDENTIAL_USERPASS_PLAINTEXT, &payload)); + cl_assert_equal_s("bob", git_credential_get_username(cred)); + git_credential_free(cred); } diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index bedbcf9e8..804308070 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -8,7 +8,7 @@ static const char* tagger_name = "Vicent Marti"; static const char* tagger_email = "vicent@github.com"; static const char* tagger_message = "This is my tag.\n\nThere are many tags, but this one is mine\n"; -static int transfer_cb(const git_transfer_progress *stats, void *payload) +static int transfer_cb(const git_indexer_progress *stats, void *payload) { int *callcount = (int*)payload; GIT_UNUSED(stats); @@ -419,7 +419,7 @@ void test_network_fetchlocal__multi_remotes(void) cl_git_pass(git_remote_fetch(test, NULL, &options, NULL)); cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(32, (int)refnames.count); + cl_assert_equal_i(33, (int)refnames.count); git_strarray_free(&refnames); cl_git_pass(git_remote_set_url(repo, "test_with_pushurl", cl_git_fixture_url("testrepo.git"))); @@ -427,7 +427,7 @@ void test_network_fetchlocal__multi_remotes(void) cl_git_pass(git_remote_fetch(test2, NULL, &options, NULL)); cl_git_pass(git_reference_list(&refnames, repo)); - cl_assert_equal_i(44, (int)refnames.count); + cl_assert_equal_i(45, (int)refnames.count); git_strarray_free(&refnames); git_remote_free(test); diff --git a/tests/network/joinpath.c b/tests/network/joinpath.c new file mode 100644 index 000000000..da8393b91 --- /dev/null +++ b/tests/network/joinpath.c @@ -0,0 +1,194 @@ +#include "clar_libgit2.h" +#include "net.h" +#include "netops.h" + +static git_net_url source, target; + +void test_network_joinpath__initialize(void) +{ + memset(&source, 0, sizeof(source)); + memset(&target, 0, sizeof(target)); +} + +void test_network_joinpath__cleanup(void) +{ + git_net_url_dispose(&source); + git_net_url_dispose(&target); +} + +void test_network_joinpath__target_paths_and_queries(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d")); + cl_assert_equal_s(target.path, "/a/b/c/d"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo")); + cl_assert_equal_s(target.path, "/a/b/c/d"); + cl_assert_equal_s(target.query, "foo"); + git_net_url_dispose(&target); +} + +void test_network_joinpath__source_query_removed(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b?query&one&two")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d")); + cl_assert_equal_s(target.path, "/a/b/c/d"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo")); + cl_assert_equal_s(target.path, "/a/b/c/d"); + cl_assert_equal_s(target.query, "foo"); + git_net_url_dispose(&target); +} + +void test_network_joinpath__source_lacks_path(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); +} + +void test_network_joinpath__source_is_slash(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com/")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); +} + + +void test_network_joinpath__source_has_query(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com?query")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "")); + cl_assert_equal_s(target.path, "/"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello")); + cl_assert_equal_s(target.path, "/asdf"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello")); + cl_assert_equal_s(target.path, "/foo/bar"); + cl_assert_equal_s(target.query, "hello"); + git_net_url_dispose(&target); +} + + +void test_network_joinpath__empty_query_ignored(void) +{ + cl_git_pass(git_net_url_parse(&source, "http://example.com/foo")); + + cl_git_pass(git_net_url_joinpath(&target, &source, "/bar/baz?")); + cl_assert_equal_s(target.path, "/foo/bar/baz"); + cl_assert_equal_p(target.query, NULL); + git_net_url_dispose(&target); +} diff --git a/tests/network/redirect.c b/tests/network/redirect.c new file mode 100644 index 000000000..7ce1310db --- /dev/null +++ b/tests/network/redirect.c @@ -0,0 +1,129 @@ +#include "clar_libgit2.h" +#include "net.h" +#include "netops.h" + +static git_net_url conndata; + +void test_network_redirect__initialize(void) +{ + memset(&conndata, 0, sizeof(conndata)); +} + +void test_network_redirect__cleanup(void) +{ + git_net_url_dispose(&conndata); +} + +void test_network_redirect__redirect_http(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "http://example.com/foo/bar/baz")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "http://example.com/foo/bar/baz", "bar/baz")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/foo/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_redirect__redirect_ssl(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://example.com/foo/bar/baz")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "https://example.com/foo/bar/baz", "bar/baz")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/foo/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_redirect__redirect_leaves_root_path(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://example.com/foo/bar/baz")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "https://example.com/foo/bar/baz", "/foo/bar/baz")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_redirect__redirect_encoded_username_password(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/foo/"); + cl_assert_equal_s(conndata.username, "user/name"); + cl_assert_equal_s(conndata.password, "pass@word%zyx%v"); +} + +void test_network_redirect__redirect_cross_host_denied(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "https://bar.com/bar/baz")); + cl_git_fail_with(git_net_url_apply_redirect(&conndata, + "https://foo.com/bar/baz", NULL), + -1); +} + +void test_network_redirect__redirect_http_downgrade_denied(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz")); + cl_git_fail_with(git_net_url_apply_redirect(&conndata, + "http://foo.com/bar/baz", NULL), + -1); +} + +void test_network_redirect__redirect_relative(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://foo.com/bar/baz/biff")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "/zap/baz/biff?bam", NULL)); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "foo.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_redirect__redirect_relative_ssl(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz/biff")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "/zap/baz/biff?bam", NULL)); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "foo.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_redirect__service_query_no_query_params_in_location(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/info/refs?service=git-upload-pack")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "/baz/info/refs", "/info/refs?service=git-upload-pack")); + cl_assert_equal_s(conndata.path, "/baz"); +} + +void test_network_redirect__service_query_with_query_params_in_location(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/info/refs?service=git-upload-pack")); + cl_git_pass(git_net_url_apply_redirect(&conndata, + "/baz/info/refs?service=git-upload-pack", "/info/refs?service=git-upload-pack")); + cl_assert_equal_s(conndata.path, "/baz"); +} diff --git a/tests/network/refspecs.c b/tests/network/refspecs.c index 1a65fd246..5c8eb1502 100644 --- a/tests/network/refspecs.c +++ b/tests/network/refspecs.c @@ -70,15 +70,18 @@ void test_network_refspecs__parsing(void) assert_refspec(GIT_DIRECTION_PUSH, ":refs/remotes/frotz/delete me", false); assert_refspec(GIT_DIRECTION_FETCH, ":refs/remotes/frotz/HEAD to me", false); - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", false); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", false); + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", true); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", true); - assert_refspec(GIT_DIRECTION_FETCH, "refs/heads*/for-linus:refs/remotes/mine/*", false); - assert_refspec(GIT_DIRECTION_PUSH, "refs/heads*/for-linus:refs/remotes/mine/*", false); + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads*/for-linus:refs/remotes/mine/*", true); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads*/for-linus:refs/remotes/mine/*", true); assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/*/for-linus:refs/remotes/mine/*", false); assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/*/for-linus:refs/remotes/mine/*", false); + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*g*/for-linus:refs/remotes/mine/*", false); + assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*g*/for-linus:refs/remotes/mine/*", false); + assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*", true); assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*", true); @@ -93,7 +96,7 @@ static void assert_valid_transform(const char *refspec, const char *name, const git_refspec spec; git_buf buf = GIT_BUF_INIT; - git_refspec__parse(&spec, refspec, true); + cl_git_pass(git_refspec__parse(&spec, refspec, true)); cl_git_pass(git_refspec_transform(&buf, &spec, name)); cl_assert_equal_s(result, buf.ptr); @@ -111,6 +114,17 @@ void test_network_refspecs__transform_mid_star(void) assert_valid_transform("refs/*:refs/*", "refs/heads/master", "refs/heads/master"); } +void test_network_refspecs__transform_loosened_star(void) +{ + assert_valid_transform("refs/heads/branch-*:refs/remotes/origin/branch-*", "refs/heads/branch-a", "refs/remotes/origin/branch-a"); + assert_valid_transform("refs/heads/branch-*/head:refs/remotes/origin/branch-*/head", "refs/heads/branch-a/head", "refs/remotes/origin/branch-a/head"); +} + +void test_network_refspecs__transform_nested_star(void) +{ + assert_valid_transform("refs/heads/x*x/for-linus:refs/remotes/mine/*", "refs/heads/xbranchx/for-linus", "refs/remotes/mine/branch"); +} + void test_network_refspecs__no_dst(void) { assert_valid_transform("refs/heads/master:", "refs/heads/master", ""); @@ -139,7 +153,7 @@ static void assert_invalid_rtransform(const char *refspec, const char *name) git_refspec spec; git_buf buf = GIT_BUF_INIT; - git_refspec__parse(&spec, refspec, true); + cl_git_pass(git_refspec__parse(&spec, refspec, true)); cl_git_fail(git_refspec_rtransform(&buf, &spec, name)); git_buf_dispose(&buf); diff --git a/tests/network/remote/local.c b/tests/network/remote/local.c index 99b91c5d0..16cce6777 100644 --- a/tests/network/remote/local.c +++ b/tests/network/remote/local.c @@ -62,7 +62,7 @@ void test_network_remote_local__retrieve_advertised_references(void) cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - cl_assert_equal_i(refs_len, 28); + cl_assert_equal_i(refs_len, 29); } void test_network_remote_local__retrieve_advertised_before_connect(void) @@ -86,7 +86,7 @@ void test_network_remote_local__retrieve_advertised_references_after_disconnect( cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - cl_assert_equal_i(refs_len, 28); + cl_assert_equal_i(refs_len, 29); } void test_network_remote_local__retrieve_advertised_references_from_spaced_repository(void) @@ -101,7 +101,7 @@ void test_network_remote_local__retrieve_advertised_references_from_spaced_repos cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); - cl_assert_equal_i(refs_len, 28); + cl_assert_equal_i(refs_len, 29); git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */ remote = NULL; diff --git a/tests/network/remote/remotes.c b/tests/network/remote/remotes.c index 10517957d..a29281d37 100644 --- a/tests/network/remote/remotes.c +++ b/tests/network/remote/remotes.c @@ -28,28 +28,97 @@ void test_network_remote_remotes__cleanup(void) void test_network_remote_remotes__parsing(void) { + git_buf url = GIT_BUF_INIT; git_remote *_remote2 = NULL; cl_assert_equal_s(git_remote_name(_remote), "test"); cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); cl_assert(git_remote_pushurl(_remote) == NULL); - cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIRECTION_FETCH), - "git://github.com/libgit2/libgit2"); - cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIRECTION_PUSH), - "git://github.com/libgit2/libgit2"); + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, NULL)); + cl_assert_equal_s(url.ptr, "git://github.com/libgit2/libgit2"); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, NULL)); + cl_assert_equal_s(url.ptr, "git://github.com/libgit2/libgit2"); cl_git_pass(git_remote_lookup(&_remote2, _repo, "test_with_pushurl")); cl_assert_equal_s(git_remote_name(_remote2), "test_with_pushurl"); cl_assert_equal_s(git_remote_url(_remote2), "git://github.com/libgit2/fetchlibgit2"); cl_assert_equal_s(git_remote_pushurl(_remote2), "git://github.com/libgit2/pushlibgit2"); - cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIRECTION_FETCH), - "git://github.com/libgit2/fetchlibgit2"); - cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIRECTION_PUSH), - "git://github.com/libgit2/pushlibgit2"); + cl_git_pass(git_remote__urlfordirection(&url, _remote2, GIT_DIRECTION_FETCH, NULL)); + cl_assert_equal_s(url.ptr, "git://github.com/libgit2/fetchlibgit2"); + + cl_git_pass(git_remote__urlfordirection(&url, _remote2, GIT_DIRECTION_PUSH, NULL)); + cl_assert_equal_s(url.ptr, "git://github.com/libgit2/pushlibgit2"); git_remote_free(_remote2); + git_buf_dispose(&url); +} + +static int urlresolve_callback(git_buf *url_resolved, const char *url, int direction, void *payload) +{ + cl_assert(strcmp(url, "git://github.com/libgit2/libgit2") == 0); + cl_assert(strcmp(payload, "payload") == 0); + cl_assert(url_resolved->size == 0); + + if (direction == GIT_DIRECTION_PUSH) + git_buf_sets(url_resolved, "pushresolve"); + if (direction == GIT_DIRECTION_FETCH) + git_buf_sets(url_resolved, "fetchresolve"); + + return GIT_OK; +} + +void test_network_remote_remotes__urlresolve(void) +{ + git_buf url = GIT_BUF_INIT; + + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + callbacks.resolve_url = urlresolve_callback; + callbacks.payload = "payload"; + + cl_assert_equal_s(git_remote_name(_remote), "test"); + cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); + cl_assert(git_remote_pushurl(_remote) == NULL); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, &callbacks)); + cl_assert_equal_s(url.ptr, "fetchresolve"); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, &callbacks)); + cl_assert_equal_s(url.ptr, "pushresolve"); + + git_buf_dispose(&url); +} + +static int urlresolve_passthrough_callback(git_buf *url_resolved, const char *url, int direction, void *payload) +{ + GIT_UNUSED(url_resolved); + GIT_UNUSED(url); + GIT_UNUSED(direction); + GIT_UNUSED(payload); + return GIT_PASSTHROUGH; +} + +void test_network_remote_remotes__urlresolve_passthrough(void) +{ + git_buf url = GIT_BUF_INIT; + const char *orig_url = "git://github.com/libgit2/libgit2"; + + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + callbacks.resolve_url = urlresolve_passthrough_callback; + + cl_assert_equal_s(git_remote_name(_remote), "test"); + cl_assert_equal_s(git_remote_url(_remote), orig_url); + cl_assert(git_remote_pushurl(_remote) == NULL); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, &callbacks)); + cl_assert_equal_s(url.ptr, orig_url); + + cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, &callbacks)); + cl_assert_equal_s(url.ptr, orig_url); + + git_buf_dispose(&url); } void test_network_remote_remotes__pushurl(void) diff --git a/tests/network/urlparse.c b/tests/network/urlparse.c index 4a3096baa..15707885a 100644 --- a/tests/network/urlparse.c +++ b/tests/network/urlparse.c @@ -1,220 +1,168 @@ #include "clar_libgit2.h" -#include "netops.h" +#include "net.h" -static char *host, *port, *path, *user, *pass; -static gitno_connection_data conndata; +static git_net_url conndata; void test_network_urlparse__initialize(void) { - host = port = path = user = pass = NULL; memset(&conndata, 0, sizeof(conndata)); } void test_network_urlparse__cleanup(void) { -#define FREE_AND_NULL(x) if (x) { git__free(x); x = NULL; } - FREE_AND_NULL(host); - FREE_AND_NULL(port); - FREE_AND_NULL(path); - FREE_AND_NULL(user); - FREE_AND_NULL(pass); - - gitno_connection_data_free_ptrs(&conndata); + git_net_url_dispose(&conndata); } void test_network_urlparse__trivial(void) { - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "http://example.com/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "8080"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_p(user, NULL); - cl_assert_equal_p(pass, NULL); + cl_git_pass(git_net_url_parse(&conndata, "http://example.com/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); } void test_network_urlparse__root(void) { - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "http://example.com/", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "8080"); - cl_assert_equal_s(path, "/"); - cl_assert_equal_p(user, NULL); - cl_assert_equal_p(pass, NULL); + cl_git_pass(git_net_url_parse(&conndata, "http://example.com/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); } -void test_network_urlparse__just_hostname(void) +void test_network_urlparse__implied_root(void) { - cl_git_fail_with(GIT_EINVALIDSPEC, - gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "http://example.com", "8080")); + cl_git_pass(git_net_url_parse(&conndata, "http://example.com")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_urlparse__implied_root_custom_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://example.com:42")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "42"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_urlparse__implied_root_empty_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://example.com:")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); } void test_network_urlparse__encoded_password(void) { - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user:pass%2fis%40bad@hostname.com:1234/", "1")); - cl_assert_equal_s(host, "hostname.com"); - cl_assert_equal_s(port, "1234"); - cl_assert_equal_s(path, "/"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_s(pass, "pass/is@bad"); + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass%2fis%40bad@hostname.com:1234/")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "hostname.com"); + cl_assert_equal_s(conndata.port, "1234"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass/is@bad"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); } void test_network_urlparse__user(void) { - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user@example.com/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "8080"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_p(pass, NULL); + cl_git_pass(git_net_url_parse(&conndata, + "https://user@example.com/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); } void test_network_urlparse__user_pass(void) { /* user:pass@hostname.tld/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user:pass@example.com/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "8080"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_s(pass, "pass"); + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass@example.com/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); } void test_network_urlparse__port(void) { /* hostname.tld:port/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://example.com:9191/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "9191"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_p(user, NULL); - cl_assert_equal_p(pass, NULL); + cl_git_pass(git_net_url_parse(&conndata, + "https://example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_network_urlparse__empty_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://example.com:/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); } void test_network_urlparse__user_port(void) { /* user@hostname.tld:port/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user@example.com:9191/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "9191"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_p(pass, NULL); + cl_git_pass(git_net_url_parse(&conndata, + "https://user@example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); } void test_network_urlparse__user_pass_port(void) { /* user:pass@hostname.tld:port/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user:pass@example.com:9191/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "9191"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_s(pass, "pass"); -} - -void test_network_urlparse__optional_path(void) -{ - cl_git_fail(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user:pass@example.com:9191", "8080")); - - cl_git_pass(gitno_extract_url_parts(&host, &port, NULL, &user, &pass, - "https://user:pass@example.com:9191", "8080")); -} - -void test_network_urlparse__connection_data_http(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "http://example.com/foo/bar/baz", "bar/baz")); + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass@example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/foo/"); - cl_assert_equal_p(conndata.user, NULL); - cl_assert_equal_p(conndata.pass, NULL); - cl_assert_equal_i(conndata.use_ssl, false); -} - -void test_network_urlparse__connection_data_ssl(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "https://example.com/foo/bar/baz", "bar/baz")); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/foo/"); - cl_assert_equal_p(conndata.user, NULL); - cl_assert_equal_p(conndata.pass, NULL); - cl_assert_equal_i(conndata.use_ssl, true); -} - -void test_network_urlparse__encoded_username_password(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz")); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/foo/"); - cl_assert_equal_s(conndata.user, "user/name"); - cl_assert_equal_s(conndata.pass, "pass@word%zyx%v"); - cl_assert_equal_i(conndata.use_ssl, true); -} - -void test_network_urlparse__connection_data_cross_host_redirect(void) -{ - conndata.host = git__strdup("bar.com"); - cl_git_fail_with(gitno_connection_data_from_url(&conndata, - "https://foo.com/bar/baz", NULL), - -1); -} - -void test_network_urlparse__connection_data_http_downgrade(void) -{ - conndata.use_ssl = true; - cl_git_fail_with(gitno_connection_data_from_url(&conndata, - "http://foo.com/bar/baz", NULL), - -1); -} - -void test_network_urlparse__connection_data_relative_redirect(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "http://foo.com/bar/baz/biff", NULL)); - cl_git_pass(gitno_connection_data_from_url(&conndata, - "/zap/baz/biff?bam", NULL)); - cl_assert_equal_s(conndata.host, "foo.com"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); - cl_assert_equal_p(conndata.user, NULL); - cl_assert_equal_p(conndata.pass, NULL); - cl_assert_equal_i(conndata.use_ssl, false); -} - -void test_network_urlparse__connection_data_relative_redirect_ssl(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "https://foo.com/bar/baz/biff", NULL)); - cl_git_pass(gitno_connection_data_from_url(&conndata, - "/zap/baz/biff?bam", NULL)); - cl_assert_equal_s(conndata.host, "foo.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); - cl_assert_equal_p(conndata.user, NULL); - cl_assert_equal_p(conndata.pass, NULL); - cl_assert_equal_i(conndata.use_ssl, true); -} - -/* Run this under valgrind */ -void test_network_urlparse__connection_data_cleanup(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "http://foo.com/bar/baz/biff", "baz/biff")); - cl_git_pass(gitno_connection_data_from_url(&conndata, - "https://foo.com/bar/baz/biff", "baz/biff")); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); } diff --git a/tests/notes/notes.c b/tests/notes/notes.c index f2457665c..af9686790 100644 --- a/tests/notes/notes.c +++ b/tests/notes/notes.c @@ -400,15 +400,11 @@ void test_notes_notes__can_read_a_note_from_a_commit(void) git_note *note; cl_git_pass(git_oid_fromstr(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045")); - cl_git_pass(git_note_commit_create(¬es_commit_oid, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 1)); - - git_commit_lookup(¬es_commit, _repo, ¬es_commit_oid); - + cl_git_pass(git_commit_lookup(¬es_commit, _repo, ¬es_commit_oid)); cl_assert(notes_commit); cl_git_pass(git_note_commit_read(¬e, _repo, notes_commit, &oid)); - cl_assert_equal_s(git_note_message(note), "I decorate 4a20\n"); git_commit_free(notes_commit); @@ -457,7 +453,7 @@ void test_notes_notes__can_insert_a_note_in_an_existing_fanout(void) git_note *_note; cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125")); - + for (i = 0; i < MESSAGES_COUNT; i++) { cl_git_pass(git_note_create(¬e_oid, _repo, "refs/notes/fanout", _sig, _sig, &target_oid, messages[i], 0)); cl_git_pass(git_note_read(&_note, _repo, "refs/notes/fanout", &target_oid)); @@ -511,7 +507,7 @@ void test_notes_notes__can_remove_a_note_from_commit(void) cl_git_pass(git_note_commit_create(¬es_commit_oid, NULL, _repo, NULL, _sig, _sig, &oid, "I decorate 4a20\n", 0)); - git_commit_lookup(&existing_notes_commit, _repo, ¬es_commit_oid); + cl_git_pass(git_commit_lookup(&existing_notes_commit, _repo, ¬es_commit_oid)); cl_assert(existing_notes_commit); @@ -547,7 +543,7 @@ void test_notes_notes__removing_a_note_which_doesnt_exists_returns_ENOTFOUND(voi cl_git_pass(git_oid_fromstr(&target_oid, "8496071c1b46c854b31185ea97743be6a8774479")); cl_git_pass(git_note_remove(_repo, "refs/notes/fanout", _sig, _sig, &target_oid)); - + error = git_note_remove(_repo, "refs/notes/fanout", _sig, _sig, &target_oid); cl_git_fail(error); cl_assert_equal_i(GIT_ENOTFOUND, error); diff --git a/tests/object/blob/filter.c b/tests/object/blob/filter.c index 002177cee..0f0f4845f 100644 --- a/tests/object/blob/filter.c +++ b/tests/object/blob/filter.c @@ -19,7 +19,7 @@ static const char *g_crlf_raw[CRLF_NUM_TEST_OBJECTS] = { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!" }; -static git_off_t g_crlf_raw_len[CRLF_NUM_TEST_OBJECTS] = { +static off64_t g_crlf_raw_len[CRLF_NUM_TEST_OBJECTS] = { -1, -1, -1, -1, -1, 17, -1, -1, 12 }; @@ -59,7 +59,7 @@ void test_object_blob_filter__initialize(void) if (g_crlf_raw_len[i] < 0) g_crlf_raw_len[i] = strlen(g_crlf_raw[i]); - cl_git_pass(git_blob_create_frombuffer( + cl_git_pass(git_blob_create_from_buffer( &g_crlf_oids[i], g_repo, g_crlf_raw[i], (size_t)g_crlf_raw_len[i])); } } diff --git a/tests/object/blob/fromstream.c b/tests/object/blob/fromstream.c index 60090b6e4..df229f98f 100644 --- a/tests/object/blob/fromstream.c +++ b/tests/object/blob/fromstream.c @@ -2,7 +2,7 @@ #include "buffer.h" #include "posix.h" #include "path.h" -#include "fileops.h" +#include "futils.h" static git_repository *repo; static char textual_content[] = "libgit2\n\r\n\0"; @@ -29,12 +29,12 @@ void test_object_blob_fromstream__multiple_write(void) cl_git_fail_with(GIT_ENOTFOUND, git_object_lookup(&blob, repo, &expected_id, GIT_OBJECT_ANY)); - cl_git_pass(git_blob_create_fromstream(&stream, repo, NULL)); + cl_git_pass(git_blob_create_from_stream(&stream, repo, NULL)); for (i = 0; i < howmany; i++) cl_git_pass(stream->write(stream, textual_content, strlen(textual_content))); - cl_git_pass(git_blob_create_fromstream_commit(&id, stream)); + cl_git_pass(git_blob_create_from_stream_commit(&id, stream)); cl_assert_equal_oid(&expected_id, &id); cl_git_pass(git_object_lookup(&blob, repo, &expected_id, GIT_OBJECT_BLOB)); @@ -67,12 +67,12 @@ static void assert_named_chunked_blob(const char *expected_sha, const char *fake cl_git_pass(git_oid_fromstr(&expected_id, expected_sha)); - cl_git_pass(git_blob_create_fromstream(&stream, repo, fake_name)); + cl_git_pass(git_blob_create_from_stream(&stream, repo, fake_name)); for (i = 0; i < howmany; i++) cl_git_pass(stream->write(stream, textual_content, strlen(textual_content))); - cl_git_pass(git_blob_create_fromstream_commit(&id, stream)); + cl_git_pass(git_blob_create_from_stream_commit(&id, stream)); cl_assert_equal_oid(&expected_id, &id); } diff --git a/tests/object/blob/write.c b/tests/object/blob/write.c index 4cf5a6607..9a18d7cd4 100644 --- a/tests/object/blob/write.c +++ b/tests/object/blob/write.c @@ -2,7 +2,7 @@ #include "buffer.h" #include "posix.h" #include "path.h" -#include "fileops.h" +#include "futils.h" static git_repository *repo; @@ -33,7 +33,7 @@ void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_file_lo { repo = cl_git_sandbox_init(WORKDIR); - assert_blob_creation(WORKDIR "/test.txt", "test.txt", &git_blob_create_fromworkdir); + assert_blob_creation(WORKDIR "/test.txt", "test.txt", &git_blob_create_from_workdir); } void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_absolute_filepath_pointing_outside_of_the_working_directory(void) @@ -46,7 +46,7 @@ void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_absolut cl_must_pass(git_path_prettify_dir(&full_path, ELSEWHERE, NULL)); cl_must_pass(git_buf_puts(&full_path, "test.txt")); - assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk); + assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_from_disk); git_buf_dispose(&full_path); cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_RMDIR_REMOVE_FILES)); @@ -62,7 +62,7 @@ void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_fi cl_must_pass(git_path_prettify_dir(&full_path, ELSEWHERE, NULL)); cl_must_pass(git_buf_puts(&full_path, "test.txt")); - assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk); + assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_from_disk); git_buf_dispose(&full_path); cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_RMDIR_REMOVE_FILES)); diff --git a/tests/object/cache.c b/tests/object/cache.c index d3ec53ac2..08bf03648 100644 --- a/tests/object/cache.c +++ b/tests/object/cache.c @@ -2,10 +2,35 @@ #include "repository.h" static git_repository *g_repo; +static size_t cache_limit; +static int object_type; -void test_object_cache__initialize(void) +void test_object_cache__initialize_cache_no_blobs(void) { g_repo = NULL; + object_type = GIT_OBJECT_BLOB; + cache_limit = 0; +} + +void test_object_cache__initialize_cache_tiny_blobs(void) +{ + g_repo = NULL; + object_type = GIT_OBJECT_BLOB; + cache_limit = 10; +} + +void test_object_cache__initialize_cache_all_blobs(void) +{ + g_repo = NULL; + object_type = GIT_OBJECT_BLOB; + cache_limit = 32767; +} + +void test_object_cache__initialize_cache_no_trees(void) +{ + g_repo = NULL; + object_type = GIT_OBJECT_TREE; + cache_limit = 0; } void test_object_cache__cleanup(void) @@ -14,47 +39,49 @@ void test_object_cache__cleanup(void) g_repo = NULL; git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJECT_BLOB, (size_t)0); + git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJECT_TREE, (size_t)4096); + git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJECT_COMMIT, (size_t)4096); } static struct { git_object_t type; const char *sha; + size_t size; } g_data[] = { /* HEAD */ - { GIT_OBJECT_BLOB, "a8233120f6ad708f843d861ce2b7228ec4e3dec6" }, /* README */ - { GIT_OBJECT_BLOB, "3697d64be941a53d4ae8f6a271e4e3fa56b022cc" }, /* branch_file.txt */ - { GIT_OBJECT_BLOB, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd" }, /* new.txt */ + { GIT_OBJECT_BLOB, "a8233120f6ad708f843d861ce2b7228ec4e3dec6", 10 }, /* README */ + { GIT_OBJECT_BLOB, "3697d64be941a53d4ae8f6a271e4e3fa56b022cc", 8 }, /* branch_file.txt */ + { GIT_OBJECT_BLOB, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd", 12 }, /* new.txt */ /* refs/heads/subtrees */ - { GIT_OBJECT_BLOB, "1385f264afb75a56a5bec74243be9b367ba4ca08" }, /* README */ - { GIT_OBJECT_TREE, "f1425cef211cc08caa31e7b545ffb232acb098c3" }, /* ab */ - { GIT_OBJECT_BLOB, "d6c93164c249c8000205dd4ec5cbca1b516d487f" }, /* ab/4.txt */ - { GIT_OBJECT_TREE, "9a03079b8a8ee85a0bee58bf9be3da8b62414ed4" }, /* ab/c */ - { GIT_OBJECT_BLOB, "270b8ea76056d5cad83af921837702d3e3c2924d" }, /* ab/c/3.txt */ - { GIT_OBJECT_TREE, "b6361fc6a97178d8fc8639fdeed71c775ab52593" }, /* ab/de */ - { GIT_OBJECT_BLOB, "e7b4ad382349ff96dd8199000580b9b1e2042eb0" }, /* ab/de/2.txt */ - { GIT_OBJECT_TREE, "3259a6bd5b57fb9c1281bb7ed3167b50f224cb54" }, /* ab/de/fgh */ - { GIT_OBJECT_BLOB, "1f67fc4386b2d171e0d21be1c447e12660561f9b" }, /* ab/de/fgh/1.txt */ - { GIT_OBJECT_BLOB, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057" }, /* branch_file.txt */ - { GIT_OBJECT_BLOB, "fa49b077972391ad58037050f2a75f74e3671e92" }, /* new.txt */ + { GIT_OBJECT_BLOB, "1385f264afb75a56a5bec74243be9b367ba4ca08", 4 }, /* README */ + { GIT_OBJECT_TREE, "f1425cef211cc08caa31e7b545ffb232acb098c3", 90 }, /* ab */ + { GIT_OBJECT_BLOB, "d6c93164c249c8000205dd4ec5cbca1b516d487f", 6 }, /* ab/4.txt */ + { GIT_OBJECT_TREE, "9a03079b8a8ee85a0bee58bf9be3da8b62414ed4", 33 }, /* ab/c */ + { GIT_OBJECT_BLOB, "270b8ea76056d5cad83af921837702d3e3c2924d", 6 }, /* ab/c/3.txt */ + { GIT_OBJECT_TREE, "b6361fc6a97178d8fc8639fdeed71c775ab52593", 63 }, /* ab/de */ + { GIT_OBJECT_BLOB, "e7b4ad382349ff96dd8199000580b9b1e2042eb0", 6 }, /* ab/de/2.txt */ + { GIT_OBJECT_TREE, "3259a6bd5b57fb9c1281bb7ed3167b50f224cb54", 33 }, /* ab/de/fgh */ + { GIT_OBJECT_BLOB, "1f67fc4386b2d171e0d21be1c447e12660561f9b", 6 }, /* ab/de/fgh/1.txt */ + { GIT_OBJECT_BLOB, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", 3 }, /* branch_file.txt */ + { GIT_OBJECT_BLOB, "fa49b077972391ad58037050f2a75f74e3671e92", 9 }, /* new.txt */ /* refs/heads/chomped */ - { GIT_OBJECT_BLOB, "0266163a49e280c4f5ed1e08facd36a2bd716bcf" }, /* readme.txt */ + { GIT_OBJECT_BLOB, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", 51 }, /* readme.txt */ - { 0, NULL }, - { 0, NULL } + { 0, NULL, 0 }, + { 0, NULL, 0 } }; -void test_object_cache__cache_everything(void) +void test_object_cache__cache_counts(void) { - int i, start; + int i, start, nonmatching = 0; git_oid oid; git_odb_object *odb_obj; git_object *obj; git_odb *odb; - git_libgit2_opts( - GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJECT_BLOB, (size_t)32767); + git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, object_type, cache_limit); cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); cl_git_pass(git_repository_odb(&odb, g_repo)); @@ -77,12 +104,16 @@ void test_object_cache__cache_everything(void) git_object_free(obj); } - cl_assert_equal_i(count + 1, (int)git_cache_size(&g_repo->objects)); + if ((g_data[i].type == object_type && g_data[i].size >= cache_limit) || + (g_data[i].type != object_type && g_data[i].type == GIT_OBJECT_BLOB)) + cl_assert_equal_i(count, (int)git_cache_size(&g_repo->objects)); + else { + cl_assert_equal_i(count + 1, (int)git_cache_size(&g_repo->objects)); + nonmatching++; + } } - cl_assert_equal_i(i, (int)git_cache_size(&g_repo->objects) - start); - - git_odb_free(odb); + cl_assert_equal_i(nonmatching, (int)git_cache_size(&g_repo->objects) - start); for (i = 0; g_data[i].sha != NULL; ++i) { int count = (int)git_cache_size(&g_repo->objects); @@ -94,48 +125,6 @@ void test_object_cache__cache_everything(void) cl_assert_equal_i(count, (int)git_cache_size(&g_repo->objects)); } -} - -void test_object_cache__cache_no_blobs(void) -{ - int i, start, nonblobs = 0; - git_oid oid; - git_odb_object *odb_obj; - git_object *obj; - git_odb *odb; - - git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJECT_BLOB, (size_t)0); - - cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_odb(&odb, g_repo)); - - start = (int)git_cache_size(&g_repo->objects); - - for (i = 0; g_data[i].sha != NULL; ++i) { - int count = (int)git_cache_size(&g_repo->objects); - - cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha)); - - /* alternate between loading raw and parsed objects */ - if ((i & 1) == 0) { - cl_git_pass(git_odb_read(&odb_obj, odb, &oid)); - cl_assert(g_data[i].type == git_odb_object_type(odb_obj)); - git_odb_object_free(odb_obj); - } else { - cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY)); - cl_assert(g_data[i].type == git_object_type(obj)); - git_object_free(obj); - } - - if (g_data[i].type == GIT_OBJECT_BLOB) - cl_assert_equal_i(count, (int)git_cache_size(&g_repo->objects)); - else { - cl_assert_equal_i(count + 1, (int)git_cache_size(&g_repo->objects)); - nonblobs++; - } - } - - cl_assert_equal_i(nonblobs, (int)git_cache_size(&g_repo->objects) - start); git_odb_free(odb); } @@ -255,15 +244,15 @@ static void *cache_quick(void *arg) void test_object_cache__fast_thread_rush(void) { - int try, th, data[THREADCOUNT*2]; + int try, th, data[THREADCOUNT]; #ifdef GIT_THREADS - git_thread t[THREADCOUNT*2]; + git_thread t[THREADCOUNT]; #endif for (try = 0; try < REPEAT; ++try) { cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); - for (th = 0; th < THREADCOUNT*2; ++th) { + for (th = 0; th < THREADCOUNT; ++th) { data[th] = th; #ifdef GIT_THREADS cl_git_pass( @@ -274,7 +263,7 @@ void test_object_cache__fast_thread_rush(void) } #ifdef GIT_THREADS - for (th = 0; th < THREADCOUNT*2; ++th) { + for (th = 0; th < THREADCOUNT; ++th) { void *rval; cl_git_pass(git_thread_join(&t[th], &rval)); cl_assert_equal_i(th, *((int *)rval)); diff --git a/tests/object/raw/write.c b/tests/object/raw/write.c index a360f04c5..9bc127680 100644 --- a/tests/object/raw/write.c +++ b/tests/object/raw/write.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "git2/odb_backend.h" -#include "fileops.h" +#include "futils.h" #include "odb.h" typedef struct object_data { diff --git a/tests/object/tree/read.c b/tests/object/tree/read.c index a0eae11d1..de2e64c68 100644 --- a/tests/object/tree/read.c +++ b/tests/object/tree/read.c @@ -73,3 +73,47 @@ void test_object_tree_read__two(void) git_object_free(obj); git_tree_free(tree); } + +#define BIGFILE "bigfile" + +#ifdef GIT_ARCH_64 +#define BIGFILE_SIZE (off_t)4294967296 +#else +# define BIGFILE_SIZE SIZE_MAX +#endif + +void test_object_tree_read__largefile(void) +{ + const git_tree_entry *entry; + git_index_entry ie; + git_commit *commit; + git_object *object; + git_index *index; + git_tree *tree; + git_oid oid; + char *buf; + + if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE")) + cl_skip(); + + cl_assert(buf = git__calloc(1, BIGFILE_SIZE)); + + memset(&ie, 0, sizeof(ie)); + ie.mode = GIT_FILEMODE_BLOB; + ie.path = BIGFILE; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_from_buffer(index, &ie, buf, BIGFILE_SIZE)); + cl_repo_commit_from_index(&oid, g_repo, NULL, 0, BIGFILE); + + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + cl_git_pass(git_commit_tree(&tree, commit)); + cl_assert(entry = git_tree_entry_byname(tree, BIGFILE)); + cl_git_pass(git_tree_entry_to_object(&object, g_repo, entry)); + + git_object_free(object); + git_tree_free(tree); + git_index_free(index); + git_commit_free(commit); + git__free(buf); +} diff --git a/tests/odb/backend/mempack.c b/tests/odb/backend/mempack.c index 55cd69f80..2eeed51aa 100644 --- a/tests/odb/backend/mempack.c +++ b/tests/odb/backend/mempack.c @@ -51,10 +51,10 @@ void test_odb_backend_mempack__exists_with_existing_objects_succeeds(void) cl_assert(git_odb_exists(_odb, &_oid) == 1); } -void test_odb_backend_mempack__blob_create_frombuffer_succeeds(void) +void test_odb_backend_mempack__blob_create_from_buffer_succeeds(void) { const char *data = "data"; - cl_git_pass(git_blob_create_frombuffer(&_oid, _repo, data, strlen(data) + 1)); + cl_git_pass(git_blob_create_from_buffer(&_oid, _repo, data, strlen(data) + 1)); cl_assert(git_odb_exists(_odb, &_oid) == 1); } diff --git a/tests/odb/freshen.c b/tests/odb/freshen.c index 1fe64309d..1ecd92a8d 100644 --- a/tests/odb/freshen.c +++ b/tests/odb/freshen.c @@ -47,7 +47,7 @@ void test_odb_freshen__loose_blob(void) set_time_wayback(&before, LOOSE_BLOB_FN); /* make sure we freshen a blob */ - cl_git_pass(git_blob_create_frombuffer(&id, repo, LOOSE_STR, CONST_STRLEN(LOOSE_STR))); + cl_git_pass(git_blob_create_from_buffer(&id, repo, LOOSE_STR, CONST_STRLEN(LOOSE_STR))); cl_assert_equal_oid(&expected_id, &id); cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_BLOB_FN, &after)); @@ -66,13 +66,13 @@ void test_odb_freshen__readonly_object(void) cl_git_pass(git_oid_fromstr(&expected_id, UNIQUE_BLOB_ID)); - cl_git_pass(git_blob_create_frombuffer(&id, repo, UNIQUE_STR, CONST_STRLEN(UNIQUE_STR))); + cl_git_pass(git_blob_create_from_buffer(&id, repo, UNIQUE_STR, CONST_STRLEN(UNIQUE_STR))); cl_assert_equal_oid(&expected_id, &id); set_time_wayback(&before, UNIQUE_BLOB_FN); cl_assert((before.st_mode & S_IWUSR) == 0); - cl_git_pass(git_blob_create_frombuffer(&id, repo, UNIQUE_STR, CONST_STRLEN(UNIQUE_STR))); + cl_git_pass(git_blob_create_from_buffer(&id, repo, UNIQUE_STR, CONST_STRLEN(UNIQUE_STR))); cl_assert_equal_oid(&expected_id, &id); cl_must_pass(p_lstat("testrepo.git/objects/" UNIQUE_BLOB_FN, &after)); diff --git a/tests/online/clone.c b/tests/online/clone.c index 6fdbbbd01..9107956bd 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -3,7 +3,7 @@ #include "git2/clone.h" #include "git2/cred_helpers.h" #include "remote.h" -#include "fileops.h" +#include "futils.h" #include "refs.h" #define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository" @@ -11,6 +11,7 @@ #define BB_REPO_URL "https://libgit3@bitbucket.org/libgit2/testgitrepository.git" #define BB_REPO_URL_WITH_PASS "https://libgit3:libgit3@bitbucket.org/libgit2/testgitrepository.git" #define BB_REPO_URL_WITH_WRONG_PASS "https://libgit3:wrong@bitbucket.org/libgit2/testgitrepository.git" +#define GOOGLESOURCE_REPO_URL "https://chromium.googlesource.com/external/github.com/sergi/go-diff" #define SSH_REPO_URL "ssh://github.com/libgit2/TestGitRepository" @@ -30,6 +31,7 @@ static char *_remote_proxy_host = NULL; static char *_remote_proxy_user = NULL; static char *_remote_proxy_pass = NULL; static char *_remote_proxy_selfsigned = NULL; +static char *_remote_expectcontinue = NULL; static int _orig_proxies_need_reset = 0; static char *_orig_http_proxy = NULL; @@ -74,6 +76,10 @@ void test_online_clone__initialize(void) _remote_proxy_user = cl_getenv("GITTEST_REMOTE_PROXY_USER"); _remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS"); _remote_proxy_selfsigned = cl_getenv("GITTEST_REMOTE_PROXY_SELFSIGNED"); + _remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE"); + + if (_remote_expectcontinue) + git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1); _orig_proxies_need_reset = 0; } @@ -99,6 +105,7 @@ void test_online_clone__cleanup(void) git__free(_remote_proxy_user); git__free(_remote_proxy_pass); git__free(_remote_proxy_selfsigned); + git__free(_remote_expectcontinue); if (_orig_proxies_need_reset) { cl_setenv("HTTP_PROXY", _orig_http_proxy); @@ -158,7 +165,7 @@ static void checkout_progress(const char *path, size_t cur, size_t tot, void *pa (*was_called) = true; } -static int fetch_progress(const git_transfer_progress *stats, void *payload) +static int fetch_progress(const git_indexer_progress *stats, void *payload) { bool *was_called = (bool*)payload; GIT_UNUSED(stats); @@ -285,7 +292,7 @@ void test_online_clone__custom_headers(void) } static int cred_failure_cb( - git_cred **cred, + git_credential **cred, const char *url, const char *username_from_url, unsigned int allowed_types, @@ -309,22 +316,22 @@ void test_online_clone__cred_callback_failure_return_code_is_tunnelled(void) cl_git_fail_with(-172, git_clone(&g_repo, _remote_url, "./foo", &g_options)); } -static int cred_count_calls_cb(git_cred **cred, const char *url, const char *user, +static int cred_count_calls_cb(git_credential **cred, const char *url, const char *user, unsigned int allowed_types, void *data) { size_t *counter = (size_t *) data; GIT_UNUSED(url); GIT_UNUSED(user); GIT_UNUSED(allowed_types); - if (allowed_types == GIT_CREDTYPE_USERNAME) - return git_cred_username_new(cred, "foo"); + if (allowed_types == GIT_CREDENTIAL_USERNAME) + return git_credential_username_new(cred, "foo"); (*counter)++; if (*counter == 3) return GIT_EUSER; - return git_cred_userpass_plaintext_new(cred, "foo", "bar"); + return git_credential_userpass_plaintext_new(cred, "foo", "bar"); } void test_online_clone__cred_callback_called_again_on_auth_failure(void) @@ -334,7 +341,7 @@ void test_online_clone__cred_callback_called_again_on_auth_failure(void) git__free(_remote_url); git__free(_remote_user); - _remote_url = git__strdup("https://github.com/libgit2/non-existent"); + _remote_url = git__strdup("https://gitlab.com/libgit2/non-existent"); _remote_user = git__strdup("libgit2test"); g_options.fetch_opts.callbacks.credentials = cred_count_calls_cb; @@ -345,7 +352,7 @@ void test_online_clone__cred_callback_called_again_on_auth_failure(void) } int cred_default( - git_cred **cred, + git_credential **cred, const char *url, const char *user_from_url, unsigned int allowed_types, @@ -355,10 +362,10 @@ int cred_default( GIT_UNUSED(user_from_url); GIT_UNUSED(payload); - if (!(allowed_types & GIT_CREDTYPE_DEFAULT)) + if (!(allowed_types & GIT_CREDENTIAL_DEFAULT)) return 0; - return git_cred_default_new(cred); + return git_credential_default_new(cred); } void test_online_clone__credentials(void) @@ -366,7 +373,7 @@ void test_online_clone__credentials(void) /* Remote URL environment variable must be set. * User and password are optional. */ - git_cred_userpass_payload user_pass = { + git_credential_userpass_payload user_pass = { _remote_user, _remote_pass }; @@ -377,7 +384,7 @@ void test_online_clone__credentials(void) if (cl_is_env_set("GITTEST_REMOTE_DEFAULT")) { g_options.fetch_opts.callbacks.credentials = cred_default; } else { - g_options.fetch_opts.callbacks.credentials = git_cred_userpass; + g_options.fetch_opts.callbacks.credentials = git_credential_userpass; g_options.fetch_opts.callbacks.payload = &user_pass; } @@ -386,13 +393,28 @@ void test_online_clone__credentials(void) cl_fixture_cleanup("./foo"); } +void test_online_clone__credentials_via_custom_headers(void) +{ + const char *creds = "libgit3:libgit3"; + git_buf auth = GIT_BUF_INIT; + + cl_git_pass(git_buf_puts(&auth, "Authorization: Basic ")); + cl_git_pass(git_buf_encode_base64(&auth, creds, strlen(creds))); + g_options.fetch_opts.custom_headers.count = 1; + g_options.fetch_opts.custom_headers.strings = &auth.ptr; + + cl_git_pass(git_clone(&g_repo, "https://bitbucket.org/libgit2/testgitrepository.git", "./foo", &g_options)); + + git_buf_dispose(&auth); +} + void test_online_clone__bitbucket_style(void) { - git_cred_userpass_payload user_pass = { + git_credential_userpass_payload user_pass = { "libgit3", "libgit3" }; - g_options.fetch_opts.callbacks.credentials = git_cred_userpass; + g_options.fetch_opts.callbacks.credentials = git_credential_userpass; g_options.fetch_opts.callbacks.payload = &user_pass; cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options)); @@ -402,16 +424,16 @@ void test_online_clone__bitbucket_style(void) void test_online_clone__bitbucket_uses_creds_in_url(void) { - git_cred_userpass_payload user_pass = { + git_credential_userpass_payload user_pass = { "libgit2", "wrong" }; - g_options.fetch_opts.callbacks.credentials = git_cred_userpass; + g_options.fetch_opts.callbacks.credentials = git_credential_userpass; g_options.fetch_opts.callbacks.payload = &user_pass; /* * Correct user and pass are in the URL; the (incorrect) creds in - * the `git_cred_userpass_payload` should be ignored. + * the `git_credential_userpass_payload` should be ignored. */ cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_PASS, "./foo", &g_options)); git_repository_free(g_repo); g_repo = NULL; @@ -420,11 +442,11 @@ void test_online_clone__bitbucket_uses_creds_in_url(void) void test_online_clone__bitbucket_falls_back_to_specified_creds(void) { - git_cred_userpass_payload user_pass = { + git_credential_userpass_payload user_pass = { "libgit2", "libgit2" }; - g_options.fetch_opts.callbacks.credentials = git_cred_userpass; + g_options.fetch_opts.callbacks.credentials = git_credential_userpass; g_options.fetch_opts.callbacks.payload = &user_pass; /* @@ -435,14 +457,21 @@ void test_online_clone__bitbucket_falls_back_to_specified_creds(void) /* * Incorrect user and pass are in the URL; the (correct) creds in - * the `git_cred_userpass_payload` should be used as a fallback. + * the `git_credential_userpass_payload` should be used as a fallback. */ cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_WRONG_PASS, "./foo", &g_options)); git_repository_free(g_repo); g_repo = NULL; cl_fixture_cleanup("./foo"); } -static int cancel_at_half(const git_transfer_progress *stats, void *payload) +void test_online_clone__googlesource(void) +{ + cl_git_pass(git_clone(&g_repo, GOOGLESOURCE_REPO_URL, "./foo", &g_options)); + git_repository_free(g_repo); g_repo = NULL; + cl_fixture_cleanup("./foo"); +} + +static int cancel_at_half(const git_indexer_progress *stats, void *payload) { GIT_UNUSED(payload); @@ -455,20 +484,20 @@ void test_online_clone__can_cancel(void) { g_options.fetch_opts.callbacks.transfer_progress = cancel_at_half; - cl_git_fail_with( - git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options), 4321); + cl_git_fail_with(4321, + git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); } -static int cred_cb(git_cred **cred, const char *url, const char *user_from_url, +static int cred_cb(git_credential **cred, const char *url, const char *user_from_url, unsigned int allowed_types, void *payload) { GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload); - if (allowed_types & GIT_CREDTYPE_USERNAME) - return git_cred_username_new(cred, _remote_user); + if (allowed_types & GIT_CREDENTIAL_USERNAME) + return git_credential_username_new(cred, _remote_user); - if (allowed_types & GIT_CREDTYPE_SSH_KEY) - return git_cred_ssh_key_new(cred, + if (allowed_types & GIT_CREDENTIAL_SSH_KEY) + return git_credential_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_privkey, _remote_ssh_passphrase); @@ -476,16 +505,16 @@ static int cred_cb(git_cred **cred, const char *url, const char *user_from_url, return -1; } -static int check_ssh_auth_methods(git_cred **cred, const char *url, const char *username_from_url, +static int check_ssh_auth_methods(git_credential **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data) { int *with_user = (int *) data; GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(data); if (!*with_user) - cl_assert_equal_i(GIT_CREDTYPE_USERNAME, allowed_types); + cl_assert_equal_i(GIT_CREDENTIAL_USERNAME, allowed_types); else - cl_assert(!(allowed_types & GIT_CREDTYPE_USERNAME)); + cl_assert(!(allowed_types & GIT_CREDENTIAL_USERNAME)); return GIT_EUSER; } @@ -560,13 +589,13 @@ void test_online_clone__ssh_with_paths(void) cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); } -static int cred_foo_bar(git_cred **cred, const char *url, const char *username_from_url, +static int cred_foo_bar(git_credential **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data) { GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(allowed_types); GIT_UNUSED(data); - return git_cred_userpass_plaintext_new(cred, "foo", "bar"); + return git_credential_userpass_plaintext_new(cred, "foo", "bar"); } void test_online_clone__ssh_cannot_change_username(void) @@ -643,20 +672,20 @@ static char *read_key_file(const char *path) return buf; } -static int ssh_memory_cred_cb(git_cred **cred, const char *url, const char *user_from_url, +static int ssh_memory_cred_cb(git_credential **cred, const char *url, const char *user_from_url, unsigned int allowed_types, void *payload) { GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload); - if (allowed_types & GIT_CREDTYPE_USERNAME) - return git_cred_username_new(cred, _remote_user); + if (allowed_types & GIT_CREDENTIAL_USERNAME) + return git_credential_username_new(cred, _remote_user); - if (allowed_types & GIT_CREDTYPE_SSH_KEY) + if (allowed_types & GIT_CREDENTIAL_SSH_KEY) { char *pubkey = read_key_file(_remote_ssh_pubkey); char *privkey = read_key_file(_remote_ssh_privkey); - int ret = git_cred_ssh_key_memory_new(cred, _remote_user, pubkey, privkey, _remote_ssh_passphrase); + int ret = git_credential_ssh_key_memory_new(cred, _remote_user, pubkey, privkey, _remote_ssh_passphrase); if (privkey) free(privkey); @@ -682,12 +711,6 @@ void test_online_clone__ssh_memory_auth(void) cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); } -void test_online_clone__url_with_no_path_returns_EINVALIDSPEC(void) -{ - cl_git_fail_with(git_clone(&g_repo, "http://github.com", "./foo", &g_options), - GIT_EINVALIDSPEC); -} - static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload) { GIT_UNUSED(cert); @@ -737,7 +760,7 @@ void test_online_clone__start_with_http(void) } static int called_proxy_creds; -static int proxy_cred_cb(git_cred **out, const char *url, const char *username, unsigned int allowed, void *payload) +static int proxy_cred_cb(git_credential **out, const char *url, const char *username, unsigned int allowed, void *payload) { GIT_UNUSED(url); GIT_UNUSED(username); @@ -745,7 +768,7 @@ static int proxy_cred_cb(git_cred **out, const char *url, const char *username, GIT_UNUSED(payload); called_proxy_creds = 1; - return git_cred_userpass_plaintext_new(out, _remote_proxy_user, _remote_proxy_pass); + return git_credential_userpass_plaintext_new(out, _remote_proxy_user, _remote_proxy_pass); } static int proxy_cert_cb(git_cert *cert, int valid, const char *host, void *payload) @@ -847,3 +870,37 @@ void test_online_clone__proxy_auto_not_detected(void) cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); } + +void test_online_clone__proxy_cred_callback_after_failed_url_creds(void) +{ + git_buf url = GIT_BUF_INIT; + + if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); + + cl_git_pass(git_buf_printf(&url, "%s://invalid_user_name:INVALID_pass_WORD@%s/", + _remote_proxy_scheme ? _remote_proxy_scheme : "http", + _remote_proxy_host)); + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + g_options.fetch_opts.proxy_opts.url = url.ptr; + g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb; + g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; + called_proxy_creds = 0; + cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_assert(called_proxy_creds); + + git_buf_dispose(&url); +} + +void test_online_clone__azurerepos(void) +{ + cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/test", "./foo", &g_options)); + cl_assert(git_path_exists("./foo/master.txt")); +} + +void test_online_clone__path_whitespace(void) +{ + cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/spaces%20in%20the%20name", "./foo", &g_options)); + cl_assert(git_path_exists("./foo/master.txt")); +} diff --git a/tests/online/fetch.c b/tests/online/fetch.c index 827cb23c1..f939a16b8 100644 --- a/tests/online/fetch.c +++ b/tests/online/fetch.c @@ -25,7 +25,7 @@ static int update_tips(const char *refname, const git_oid *a, const git_oid *b, return 0; } -static int progress(const git_transfer_progress *stats, void *payload) +static int progress(const git_indexer_progress *stats, void *payload) { size_t *bytes_received = (size_t *)payload; *bytes_received = stats->received_bytes; @@ -84,15 +84,15 @@ void test_online_fetch__fetch_twice(void) cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_git_pass(git_remote_download(remote, NULL, NULL)); git_remote_disconnect(remote); - + git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL); cl_git_pass(git_remote_download(remote, NULL, NULL)); git_remote_disconnect(remote); - + git_remote_free(remote); } -static int transferProgressCallback(const git_transfer_progress *stats, void *payload) +static int transferProgressCallback(const git_indexer_progress *stats, void *payload) { bool *invoked = (bool *)payload; @@ -134,7 +134,7 @@ void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date git_repository_free(_repository); } -static int cancel_at_half(const git_transfer_progress *stats, void *payload) +static int cancel_at_half(const git_indexer_progress *stats, void *payload) { GIT_UNUSED(payload); diff --git a/tests/online/fetchhead.c b/tests/online/fetchhead.c index ae72dde57..32e7419ae 100644 --- a/tests/online/fetchhead.c +++ b/tests/online/fetchhead.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "fetchhead.h" #include "../fetchhead/fetchhead_data.h" #include "git2/clone.h" @@ -35,10 +35,10 @@ static void fetchhead_test_clone(void) cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); } -static int count_references(void) +static size_t count_references(void) { git_strarray array; - int refs; + size_t refs; cl_git_pass(git_reference_list(&array, g_repo)); refs = array.count; @@ -118,7 +118,7 @@ void test_online_fetchhead__no_merges(void) void test_online_fetchhead__explicit_dst_refspec_creates_branch(void) { git_reference *ref; - int refs; + size_t refs; fetchhead_test_clone(); refs = count_references(); @@ -133,7 +133,7 @@ void test_online_fetchhead__explicit_dst_refspec_creates_branch(void) void test_online_fetchhead__empty_dst_refspec_creates_no_branch(void) { git_reference *ref; - int refs; + size_t refs; fetchhead_test_clone(); refs = count_references(); @@ -146,7 +146,7 @@ void test_online_fetchhead__empty_dst_refspec_creates_no_branch(void) void test_online_fetchhead__colon_only_dst_refspec_creates_no_branch(void) { - int refs; + size_t refs; fetchhead_test_clone(); refs = count_references(); @@ -154,3 +154,20 @@ void test_online_fetchhead__colon_only_dst_refspec_creates_no_branch(void) cl_assert_equal_i(refs, count_references()); } + +void test_online_fetchhead__creds_get_stripped(void) +{ + git_buf buf = GIT_BUF_INIT; + git_remote *remote; + + cl_git_pass(git_repository_init(&g_repo, "./foo", 0)); + cl_git_pass(git_remote_create_anonymous(&remote, g_repo, "https://foo:bar@github.com/libgit2/TestGitRepository")); + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); + + cl_git_pass(git_futils_readbuffer(&buf, "./foo/.git/FETCH_HEAD")); + cl_assert_equal_s(buf.ptr, + "49322bb17d3acc9146f98c97d078513228bbf3c0\t\thttps://github.com/libgit2/TestGitRepository\n"); + + git_remote_free(remote); + git_buf_dispose(&buf); +} diff --git a/tests/online/push.c b/tests/online/push.c index 592372ba7..c82b606cb 100644 --- a/tests/online/push.c +++ b/tests/online/push.c @@ -19,8 +19,9 @@ static char *_remote_ssh_pubkey = NULL; static char *_remote_ssh_passphrase = NULL; static char *_remote_default = NULL; +static char *_remote_expectcontinue = NULL; -static int cred_acquire_cb(git_cred **, const char *, const char *, unsigned int, void *); +static int cred_acquire_cb(git_credential **, const char *, const char *, unsigned int, void *); static git_remote *_remote; static record_callbacks_data _record_cbs_data = {{ 0 }}; @@ -40,7 +41,7 @@ static git_oid _tag_lightweight; static git_oid _tag_tag; static int cred_acquire_cb( - git_cred **cred, + git_credential **cred, const char *url, const char *user_from_url, unsigned int allowed_types, @@ -50,40 +51,40 @@ static int cred_acquire_cb( GIT_UNUSED(user_from_url); GIT_UNUSED(payload); - if (GIT_CREDTYPE_USERNAME & allowed_types) { + if (GIT_CREDENTIAL_USERNAME & allowed_types) { if (!_remote_user) { printf("GITTEST_REMOTE_USER must be set\n"); return -1; } - return git_cred_username_new(cred, _remote_user); + return git_credential_username_new(cred, _remote_user); } - if (GIT_CREDTYPE_DEFAULT & allowed_types) { + if (GIT_CREDENTIAL_DEFAULT & allowed_types) { if (!_remote_default) { printf("GITTEST_REMOTE_DEFAULT must be set to use NTLM/Negotiate credentials\n"); return -1; } - return git_cred_default_new(cred); + return git_credential_default_new(cred); } - if (GIT_CREDTYPE_SSH_KEY & allowed_types) { + if (GIT_CREDENTIAL_SSH_KEY & allowed_types) { if (!_remote_user || !_remote_ssh_pubkey || !_remote_ssh_key || !_remote_ssh_passphrase) { printf("GITTEST_REMOTE_USER, GITTEST_REMOTE_SSH_PUBKEY, GITTEST_REMOTE_SSH_KEY and GITTEST_REMOTE_SSH_PASSPHRASE must be set\n"); return -1; } - return git_cred_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase); + return git_credential_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase); } - if (GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) { + if (GIT_CREDENTIAL_USERPASS_PLAINTEXT & allowed_types) { if (!_remote_user || !_remote_pass) { printf("GITTEST_REMOTE_USER and GITTEST_REMOTE_PASS must be set\n"); return -1; } - return git_cred_userpass_plaintext_new(cred, _remote_user, _remote_pass); + return git_credential_userpass_plaintext_new(cred, _remote_user, _remote_pass); } return -1; @@ -366,12 +367,16 @@ void test_online_push__initialize(void) _remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY"); _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); _remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT"); + _remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE"); _remote = NULL; /* Skip the test if we're missing the remote URL */ if (!_remote_url) cl_skip(); + if (_remote_expectcontinue) + git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1); + cl_git_pass(git_remote_create(&_remote, _repo, "test", _remote_url)); record_callbacks_data_clear(&_record_cbs_data); @@ -417,10 +422,13 @@ void test_online_push__cleanup(void) git__free(_remote_ssh_pubkey); git__free(_remote_ssh_passphrase); git__free(_remote_default); + git__free(_remote_expectcontinue); /* Freed by cl_git_sandbox_cleanup */ _repo = NULL; + git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 0); + record_callbacks_data_clear(&_record_cbs_data); cl_fixture_cleanup("testrepo.git"); diff --git a/tests/online/push_util.h b/tests/online/push_util.h index 570873cfe..d829bbc4a 100644 --- a/tests/online/push_util.h +++ b/tests/online/push_util.h @@ -12,7 +12,7 @@ extern const git_oid OID_ZERO; * @param data pointer to a record_callbacks_data instance */ #define RECORD_CALLBACKS_INIT(data) \ - { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, NULL, NULL, NULL, NULL, NULL, data } + { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, NULL, NULL, NULL, NULL, NULL, data, NULL } typedef struct { char *name; diff --git a/tests/pack/indexer.c b/tests/pack/indexer.c index 3c4e1c1ee..422c3def4 100644 --- a/tests/pack/indexer.c +++ b/tests/pack/indexer.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" #include -#include "fileops.h" +#include "futils.h" #include "hash.h" #include "iterator.h" #include "vector.h" @@ -98,7 +98,7 @@ static const unsigned int base_obj_len = 2; void test_pack_indexer__out_of_order(void) { git_indexer *idx = 0; - git_transfer_progress stats = { 0 }; + git_indexer_progress stats = { 0 }; cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); cl_git_pass(git_indexer_append( @@ -115,7 +115,7 @@ void test_pack_indexer__out_of_order(void) void test_pack_indexer__missing_trailer(void) { git_indexer *idx = 0; - git_transfer_progress stats = { 0 }; + git_indexer_progress stats = { 0 }; cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); cl_git_pass(git_indexer_append( @@ -131,7 +131,7 @@ void test_pack_indexer__missing_trailer(void) void test_pack_indexer__leaky(void) { git_indexer *idx = 0; - git_transfer_progress stats = { 0 }; + git_indexer_progress stats = { 0 }; cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); cl_git_pass(git_indexer_append( @@ -147,7 +147,7 @@ void test_pack_indexer__leaky(void) void test_pack_indexer__fix_thin(void) { git_indexer *idx = NULL; - git_transfer_progress stats = { 0 }; + git_indexer_progress stats = { 0 }; git_repository *repo; git_odb *odb; git_oid id, should_id; @@ -213,7 +213,7 @@ void test_pack_indexer__fix_thin(void) void test_pack_indexer__corrupt_length(void) { git_indexer *idx = NULL; - git_transfer_progress stats = { 0 }; + git_indexer_progress stats = { 0 }; git_repository *repo; git_odb *odb; git_oid id, should_id; @@ -243,7 +243,7 @@ void test_pack_indexer__incomplete_pack_fails_with_strict(void) { git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; git_indexer *idx = 0; - git_transfer_progress stats = { 0 }; + git_indexer_progress stats = { 0 }; opts.verify = 1; @@ -263,7 +263,7 @@ void test_pack_indexer__out_of_order_with_connectivity_checks(void) { git_indexer_options opts = GIT_INDEXER_OPTIONS_INIT; git_indexer *idx = 0; - git_transfer_progress stats = { 0 }; + git_indexer_progress stats = { 0 }; opts.verify = 1; diff --git a/tests/pack/packbuilder.c b/tests/pack/packbuilder.c index 6859001fe..32f061232 100644 --- a/tests/pack/packbuilder.c +++ b/tests/pack/packbuilder.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "pack.h" #include "hash.h" #include "iterator.h" @@ -12,7 +12,9 @@ static git_packbuilder *_packbuilder; static git_indexer *_indexer; static git_vector _commits; static int _commits_is_initialized; -static git_transfer_progress _stats; +static git_indexer_progress _stats; + +extern bool git_disable_pack_keep_file_checks; void test_pack_packbuilder__initialize(void) { @@ -32,6 +34,7 @@ void test_pack_packbuilder__cleanup(void) unsigned int i; cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 0)); + cl_git_pass(git_libgit2_opts(GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, false)); if (_commits_is_initialized) { _commits_is_initialized = 0; @@ -85,14 +88,14 @@ static void seed_packbuilder(void) static int feed_indexer(void *ptr, size_t len, void *payload) { - git_transfer_progress *stats = (git_transfer_progress *)payload; + git_indexer_progress *stats = (git_indexer_progress *)payload; return git_indexer_append(_indexer, ptr, len, stats); } void test_pack_packbuilder__create_pack(void) { - git_transfer_progress stats; + git_indexer_progress stats; git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT; git_hash_ctx ctx; git_oid hash; @@ -142,7 +145,7 @@ void test_pack_packbuilder__get_hash(void) seed_packbuilder(); - git_packbuilder_write(_packbuilder, ".", 0, NULL, NULL); + cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0, NULL, NULL)); git_oid_fmt(hex, git_packbuilder_hash(_packbuilder)); cl_assert_equal_s(hex, "7f5fa362c664d68ba7221259be1cbd187434b2f0"); @@ -155,7 +158,7 @@ static void test_write_pack_permission(mode_t given, mode_t expected) seed_packbuilder(); - git_packbuilder_write(_packbuilder, ".", given, NULL, NULL); + cl_git_pass(git_packbuilder_write(_packbuilder, ".", given, NULL, NULL)); /* Windows does not return group/user bits from stat, * files are never executable. @@ -194,7 +197,7 @@ void test_pack_packbuilder__permissions_readwrite(void) void test_pack_packbuilder__does_not_fsync_by_default(void) { seed_packbuilder(); - git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL); + cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL)); cl_assert_equal_sz(0, p_fsync__cnt); } @@ -212,7 +215,7 @@ void test_pack_packbuilder__fsync_global_setting(void) cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 1)); p_fsync__cnt = 0; seed_packbuilder(); - git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL); + cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL)); cl_assert_equal_sz(expected_fsyncs, p_fsync__cnt); } @@ -221,7 +224,7 @@ void test_pack_packbuilder__fsync_repo_setting(void) cl_repo_set_bool(_repo, "core.fsyncObjectFiles", true); p_fsync__cnt = 0; seed_packbuilder(); - git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL); + cl_git_pass(git_packbuilder_write(_packbuilder, ".", 0666, NULL, NULL)); cl_assert_equal_sz(expected_fsyncs, p_fsync__cnt); } @@ -260,3 +263,10 @@ void test_pack_packbuilder__foreach_with_cancel(void) git_packbuilder_foreach(_packbuilder, foreach_cancel_cb, idx), -1111); git_indexer_free(idx); } + +void test_pack_packbuilder__keep_file_check(void) +{ + assert(!git_disable_pack_keep_file_checks); + cl_git_pass(git_libgit2_opts(GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, true)); + assert(git_disable_pack_keep_file_checks); +} diff --git a/tests/pack/sharing.c b/tests/pack/sharing.c index f6f78ca03..eaf7686b7 100644 --- a/tests/pack/sharing.c +++ b/tests/pack/sharing.c @@ -24,13 +24,13 @@ void test_pack_sharing__open_two_repos(void) cl_git_pass(git_object_lookup(&obj2, repo2, &id, GIT_OBJECT_ANY)); pos = 0; - while ((error = git_strmap_next(&data, &pos, git__pack_cache)) == 0) { + while ((error = git_strmap_iterate(&data, git__pack_cache, &pos, NULL)) == 0) { struct git_pack_file *pack = (struct git_pack_file *) data; cl_assert_equal_i(2, pack->refcount.val); } - cl_assert_equal_i(3, git_strmap_num_entries(git__pack_cache)); + cl_assert_equal_i(3, git_strmap_size(git__pack_cache)); git_object_free(obj1); git_object_free(obj2); @@ -38,5 +38,5 @@ void test_pack_sharing__open_two_repos(void) git_repository_free(repo2); /* we don't want to keep the packs open after the repos go away */ - cl_assert_equal_i(0, git_strmap_num_entries(git__pack_cache)); + cl_assert_equal_i(0, git_strmap_size(git__pack_cache)); } diff --git a/tests/patch/parse.c b/tests/patch/parse.c index a40ad7b23..3f89eb51a 100644 --- a/tests/patch/parse.c +++ b/tests/patch/parse.c @@ -27,6 +27,18 @@ static void ensure_patch_validity(git_patch *patch) cl_assert_equal_i(0, delta->new_file.size); } +static void ensure_identical_patch_inout(const char *content) { + git_buf buf = GIT_BUF_INIT; + git_patch *patch; + + cl_git_pass(git_patch_from_buffer(&patch, content, strlen(content), NULL)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + cl_assert_equal_strn(git_buf_cstr(&buf), content, strlen(content)); + + git_patch_free(patch); + git_buf_dispose(&buf); +} + void test_patch_parse__original_to_change_middle(void) { git_patch *patch; @@ -102,9 +114,106 @@ void test_patch_parse__invalid_patches_fails(void) strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL)); } +void test_patch_parse__no_newline_at_end_of_new_file(void) +{ + ensure_identical_patch_inout(PATCH_APPEND_NO_NL); +} + +void test_patch_parse__no_newline_at_end_of_old_file(void) +{ + ensure_identical_patch_inout(PATCH_APPEND_NO_NL_IN_OLD_FILE); +} + void test_patch_parse__files_with_whitespaces_succeeds(void) { + ensure_identical_patch_inout(PATCH_NAME_WHITESPACE); +} + +void test_patch_parse__lifetime_of_patch_does_not_depend_on_buffer(void) +{ + git_buf diff = GIT_BUF_INIT, rendered = GIT_BUF_INIT; git_patch *patch; - cl_git_pass(git_patch_from_buffer(&patch, PATCH_NAME_WHITESPACE, strlen(PATCH_NAME_WHITESPACE), NULL)); + + cl_git_pass(git_buf_sets(&diff, PATCH_ORIGINAL_TO_CHANGE_MIDDLE)); + cl_git_pass(git_patch_from_buffer(&patch, diff.ptr, diff.size, NULL)); + git_buf_dispose(&diff); + + cl_git_pass(git_patch_to_buf(&rendered, patch)); + cl_assert_equal_s(PATCH_ORIGINAL_TO_CHANGE_MIDDLE, rendered.ptr); + git_buf_dispose(&rendered); + + cl_git_pass(git_patch_to_buf(&rendered, patch)); + cl_assert_equal_s(PATCH_ORIGINAL_TO_CHANGE_MIDDLE, rendered.ptr); + git_buf_dispose(&rendered); + + git_patch_free(patch); +} + +void test_patch_parse__binary_file_with_missing_paths(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_MISSING_PATHS, + strlen(PATCH_BINARY_FILE_WITH_MISSING_PATHS), NULL)); +} + +void test_patch_parse__binary_file_with_whitespace_paths(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS, + strlen(PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS), NULL)); +} + +void test_patch_parse__binary_file_with_empty_quoted_paths(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS, + strlen(PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS), NULL)); +} + +void test_patch_parse__binary_file_path_with_spaces(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_PATH_WITH_SPACES, + strlen(PATCH_BINARY_FILE_PATH_WITH_SPACES), NULL)); +} + +void test_patch_parse__binary_file_path_without_body_paths(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS, + strlen(PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS), NULL)); +} + +void test_patch_parse__binary_file_with_truncated_delta(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA, + strlen(PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA), NULL)); + cl_assert_equal_s(git_error_last()->message, "truncated binary data at line 5"); +} + +void test_patch_parse__memory_leak_on_multiple_paths(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_MULTIPLE_OLD_PATHS, strlen(PATCH_MULTIPLE_OLD_PATHS), NULL)); +} + +void test_patch_parse__truncated_no_newline_at_end_of_file(void) +{ + size_t len = strlen(PATCH_APPEND_NO_NL) - strlen("at end of file\n"); + const git_diff_line *line; + git_patch *patch; + + cl_git_pass(git_patch_from_buffer(&patch, PATCH_APPEND_NO_NL, len, NULL)); + cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 4)); + cl_assert_equal_s(line->content, "\\ No newline "); + + git_patch_free(patch); +} + +void test_patch_parse__line_number_overflow(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_INTMAX_NEW_LINES, strlen(PATCH_INTMAX_NEW_LINES), NULL)); git_patch_free(patch); } diff --git a/tests/patch/patch_common.h b/tests/patch/patch_common.h index 291ece9eb..731524767 100644 --- a/tests/patch/patch_common.h +++ b/tests/patch/patch_common.h @@ -263,6 +263,32 @@ "-(this line is changed)\n" \ "+(THIS line is changed!)\n" +/* A change in the middle and a deletion of the newline at the end of the file */ + +#define FILE_CHANGE_MIDDLE_AND_LASTLINE \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(THIS line is changed!)\n" \ + "and this\n" \ + "is additional context\n" \ + "BELOW it! - (THIS line is changed!)" + +#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_AND_LASTLINE_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..e05d36c 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -6 +6 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + "@@ -9 +9 @@ is additional context\n" \ + "-below it!\n" \ + "+BELOW it! - (THIS line is changed!)\n" \ + "\\ No newline at end of file\n" + /* A deletion at the beginning of the file and a change in the middle */ #define FILE_DELETE_AND_CHANGE \ @@ -359,6 +385,38 @@ "@@ -9,0 +10 @@ below it!\n" \ "+insert at end\n" +#define PATCH_DELETED_FILE_2_HUNKS \ + "diff --git a/a b/a\n" \ + "index 7f129fd..af431f2 100644\n" \ + "--- a/a\n" \ + "+++ b/a\n" \ + "@@ -1 +1 @@\n" \ + "-a contents 2\n" \ + "+a contents\n" \ + "diff --git a/c/d b/c/d\n" \ + "deleted file mode 100644\n" \ + "index 297efb8..0000000\n" \ + "--- a/c/d\n" \ + "+++ /dev/null\n" \ + "@@ -1 +0,0 @@\n" \ + "-c/d contents\n" + +#define PATCH_DELETED_FILE_2_HUNKS_SHUFFLED \ + "diff --git a/c/d b/c/d\n" \ + "deleted file mode 100644\n" \ + "index 297efb8..0000000\n" \ + "--- a/c/d\n" \ + "+++ /dev/null\n" \ + "@@ -1 +0,0 @@\n" \ + "-c/d contents\n" \ + "diff --git a/a b/a\n" \ + "index 7f129fd..af431f2 100644\n" \ + "--- a/a\n" \ + "+++ b/a\n" \ + "@@ -1 +1 @@\n" \ + "-a contents 2\n" \ + "+a contents\n" + #define PATCH_SIMPLE_COMMIT \ "commit 15e119375018fba121cf58e02a9f17fe22df0df8\n" \ "Author: Edward Thomson \n" \ @@ -681,6 +739,16 @@ "+added line with no nl\n" \ "\\ No newline at end of file\n" +#define PATCH_APPEND_NO_NL_IN_OLD_FILE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..83759c0 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,1 +1,1 @@\n" \ + "-foo\n" \ + "\\ No newline at end of file\n" \ + "+foo\n" + #define PATCH_NAME_WHITESPACE \ "diff --git a/file with spaces.txt b/file with spaces.txt\n" \ "index 9432026..83759c0 100644\n" \ @@ -842,6 +910,12 @@ "index 27184d9..7c94f9e 100644\n" \ "Binary files a/binary.bin and b/binary.bin differ\n" +#define PATCH_ADD_BINARY_NOT_PRINTED \ + "diff --git a/test.bin b/test.bin\n" \ + "new file mode 100644\n" \ + "index 0000000..9e0f96a\n" \ + "Binary files /dev/null and b/test.bin differ\n" + #define PATCH_ORIGINAL_NEW_FILE_WITH_SPACE \ "diff --git a/sp ace.txt b/sp ace.txt\n" \ "new file mode 100644\n" \ @@ -859,3 +933,65 @@ "+++ b/test-file\r\n" \ "@@ -0,0 +1 @@\r\n" \ "+a contents\r\n" + +#define PATCH_NO_EXTENDED_HEADERS \ + "diff --git a/file b/file\n" \ + "--- a/file\n" \ + "+++ b/file\n" \ + "@@ -1,3 +1,3 @@\n" \ + " a\n" \ + "-b\n" \ + "+bb\n" \ + " c\n" + +#define PATCH_BINARY_FILE_WITH_MISSING_PATHS \ + "diff --git \n" \ + "--- \n" \ + "+++ \n" \ + "Binary files " + +#define PATCH_BINARY_FILE_WITH_WHITESPACE_PATHS \ + "diff --git a/file b/file\n" \ + "--- \n" \ + "+++ \n" \ + "Binary files " + +#define PATCH_BINARY_FILE_WITH_QUOTED_EMPTY_PATHS \ + "diff --git a/file b/file\n" \ + "--- \"\"\n" \ + "+++ \"\"\n" \ + "Binary files " + +#define PATCH_BINARY_FILE_PATH_WITH_SPACES \ + "diff --git a b c d e f\n" \ + "--- a b c\n" \ + "+++ d e f\n" \ + "Binary files a b c and d e f differ" + +#define PATCH_BINARY_FILE_PATH_WITHOUT_BODY_PATHS \ + "diff --git a b c d e f\n" \ + "--- \n" \ + "+++ \n" \ + "Binary files a b c and d e f differ" + +#define PATCH_BINARY_FILE_WITH_TRUNCATED_DELTA \ + "diff --git a/file b/file\n" \ + "index 1420..b71f\n" \ + "GIT binary patch\n" \ + "delta 7\n" \ + "d" + +#define PATCH_MULTIPLE_OLD_PATHS \ + "diff --git \n" \ + "--- \n" \ + "+++ \n" \ + "index 0000..7DDb\n" \ + "--- \n" + +#define PATCH_INTMAX_NEW_LINES \ + "diff --git a/file b/file\n" \ + "--- a/file\n" \ + "+++ b/file\n" \ + "@@ -0 +2147483647 @@\n" \ + "\n" \ + " " diff --git a/tests/patch/print.c b/tests/patch/print.c index 4703c1d51..c4ff479e9 100644 --- a/tests/patch/print.c +++ b/tests/patch/print.c @@ -172,3 +172,9 @@ void test_patch_print__binary_not_shown(void) patch_print_from_patchfile(PATCH_BINARY_NOT_PRINTED, strlen(PATCH_BINARY_NOT_PRINTED)); } + +void test_patch_print__binary_add_not_shown(void) +{ + patch_print_from_patchfile(PATCH_ADD_BINARY_NOT_PRINTED, + strlen(PATCH_ADD_BINARY_NOT_PRINTED)); +} diff --git a/tests/path/core.c b/tests/path/core.c index 95e5c8c94..48b518c8d 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -343,6 +343,16 @@ void test_path_core__join_unrooted(void) test_join_unrooted("c:/foo", 2, "c:/foo", "c:/asdf"); test_join_unrooted("c:/foo/bar", 2, "c:/foo/bar", "c:/asdf"); +#ifdef GIT_WIN32 + /* Paths starting with '\\' are absolute */ + test_join_unrooted("\\bar", 0, "\\bar", "c:/foo/"); + test_join_unrooted("\\\\network\\bar", 9, "\\\\network\\bar", "c:/foo/"); +#else + /* Paths starting with '\\' are not absolute on non-Windows systems */ + test_join_unrooted("/foo/\\bar", 4, "\\bar", "/foo"); + test_join_unrooted("c:/foo/\\bar", 7, "\\bar", "c:/foo/"); +#endif + /* Base is returned when it's provided and is the prefix */ test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo"); test_join_unrooted("c:/foo/bar/foobar", 10, "c:/foo/bar/foobar", "c:/foo/bar"); diff --git a/tests/path/win32.c b/tests/path/win32.c index 3ed7d7a6a..b416e9e60 100644 --- a/tests/path/win32.c +++ b/tests/path/win32.c @@ -21,6 +21,21 @@ void test_utf8_to_utf16(const char *utf8_in, const wchar_t *utf16_expected) #endif } +void test_utf8_to_utf16_relative(const char* utf8_in, const wchar_t* utf16_expected) +{ +#ifdef GIT_WIN32 + git_win32_path path_utf16; + int path_utf16len; + + cl_assert((path_utf16len = git_win32_path_relative_from_utf8(path_utf16, utf8_in)) >= 0); + cl_assert_equal_wcs(utf16_expected, path_utf16); + cl_assert_equal_i(wcslen(utf16_expected), path_utf16len); +#else + GIT_UNUSED(utf8_in); + GIT_UNUSED(utf16_expected); +#endif +} + void test_path_win32__utf8_to_utf16(void) { #ifdef GIT_WIN32 @@ -129,6 +144,31 @@ void test_path_win32__absolute_from_relative(void) #endif } +void test_path_win32__keeps_relative(void) +{ +#ifdef GIT_WIN32 + /* Relative paths stay relative */ + test_utf8_to_utf16_relative("Foo", L"Foo"); + test_utf8_to_utf16_relative("..\\..\\Foo", L"..\\..\\Foo"); + test_utf8_to_utf16_relative("Foo\\..", L"Foo\\.."); + test_utf8_to_utf16_relative("Foo\\..\\..", L"Foo\\..\\.."); + test_utf8_to_utf16_relative("Foo\\Bar", L"Foo\\Bar"); + test_utf8_to_utf16_relative("Foo\\..\\Bar", L"Foo\\..\\Bar"); + test_utf8_to_utf16_relative("../../Foo", L"..\\..\\Foo"); + test_utf8_to_utf16_relative("Foo/..", L"Foo\\.."); + test_utf8_to_utf16_relative("Foo/../..", L"Foo\\..\\.."); + test_utf8_to_utf16_relative("Foo/Bar", L"Foo\\Bar"); + test_utf8_to_utf16_relative("Foo/../Bar", L"Foo\\..\\Bar"); + test_utf8_to_utf16_relative("Foo/../Bar/", L"Foo\\..\\Bar\\"); + test_utf8_to_utf16_relative("", L""); + + /* Absolute paths are canonicalized */ + test_utf8_to_utf16_relative("\\Foo", L"\\\\?\\C:\\Foo"); + test_utf8_to_utf16_relative("/Foo/Bar/", L"\\\\?\\C:\\Foo\\Bar"); + test_utf8_to_utf16_relative("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path"); +#endif +} + #ifdef GIT_WIN32 static void test_canonicalize(const wchar_t *in, const wchar_t *expected) { @@ -203,16 +243,6 @@ void test_path_win32__canonicalize(void) test_canonicalize(L"C:/Foo/Bar", L"C:\\Foo\\Bar"); test_canonicalize(L"C:/", L"C:\\"); - test_canonicalize(L"Foo\\\\Bar\\\\Asdf\\\\", L"Foo\\Bar\\Asdf"); - test_canonicalize(L"Foo\\\\Bar\\\\..\\\\Asdf\\", L"Foo\\Asdf"); - test_canonicalize(L"Foo\\\\Bar\\\\.\\\\Asdf\\", L"Foo\\Bar\\Asdf"); - test_canonicalize(L"Foo\\\\..\\Bar\\\\.\\\\Asdf\\", L"Bar\\Asdf"); - test_canonicalize(L"\\", L""); - test_canonicalize(L"", L""); - test_canonicalize(L"Foo\\..\\..\\..\\..", L""); - test_canonicalize(L"..\\..\\..\\..", L""); - test_canonicalize(L"\\..\\..\\..\\..", L""); - test_canonicalize(L"\\\\?\\C:\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar"); test_canonicalize(L"\\\\?\\C:\\Foo\\Bar\\", L"\\\\?\\C:\\Foo\\Bar"); test_canonicalize(L"\\\\?\\C:\\\\Foo\\.\\Bar\\\\..\\", L"\\\\?\\C:\\Foo"); diff --git a/tests/rebase/merge.c b/tests/rebase/merge.c index cccaac7b8..d24e4facf 100644 --- a/tests/rebase/merge.c +++ b/tests/rebase/merge.c @@ -44,6 +44,10 @@ void test_rebase_merge__next(void) git_status_list *status_list; const git_status_entry *status_entry; git_oid pick_id, file1_id; + git_oid master_id, beef_id; + + git_oid_fromstr(&master_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + git_oid_fromstr(&beef_id, "b146bd7608eac53d9bf9e1a6963543588b555c64"); cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); @@ -53,6 +57,12 @@ void test_rebase_merge__next(void) cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + cl_assert_equal_s("refs/heads/beef", git_rebase_orig_head_name(rebase)); + cl_assert_equal_oid(&beef_id, git_rebase_orig_head_id(rebase)); + + cl_assert_equal_s("master", git_rebase_onto_name(rebase)); + cl_assert_equal_oid(&master_id, git_rebase_onto_id(rebase)); + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); git_oid_fromstr(&pick_id, "da9c51a23d02d931a486f45ad18cda05cf5d2b94"); diff --git a/tests/rebase/sign.c b/tests/rebase/sign.c new file mode 100644 index 000000000..fa4776661 --- /dev/null +++ b/tests/rebase/sign.c @@ -0,0 +1,243 @@ +#include "clar_libgit2.h" +#include "git2/rebase.h" + +static git_repository *repo; +static git_signature *signature; + +/* Fixture setup and teardown */ +void test_rebase_sign__initialize(void) +{ + repo = cl_git_sandbox_init("rebase"); + cl_git_pass(git_signature_new(&signature, "Rebaser", + "rebaser@rebaser.rb", 1405694510, 0)); +} + +void test_rebase_sign__cleanup(void) +{ + git_signature_free(signature); + cl_git_sandbox_cleanup(); +} + +static const char *expected_commit_content = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ +parent f87d14a4a236582a0278a916340a793714256864\n\ +author Edward Thomson 1405625055 -0400\n\ +committer Rebaser 1405694510 +0000\n\ +\n\ +Modification 3 to gravy\n"; + +int signing_cb_passthrough( + git_buf *signature, + git_buf *signature_field, + const char *commit_content, + void *payload) +{ + cl_assert_equal_b(false, git_buf_is_allocated(signature)); + cl_assert_equal_b(false, git_buf_is_allocated(signature_field)); + cl_assert_equal_s(expected_commit_content, commit_content); + cl_assert_equal_p(NULL, payload); + return GIT_PASSTHROUGH; +} + +/* git checkout gravy ; git rebase --merge veal */ +void test_rebase_sign__passthrough_signing_cb(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_id; + git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; + git_commit *commit; + const char *expected_commit_raw_header = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ +parent f87d14a4a236582a0278a916340a793714256864\n\ +author Edward Thomson 1405625055 -0400\n\ +committer Rebaser 1405694510 +0000\n"; + + rebase_opts.signing_cb = signing_cb_passthrough; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + git_oid_fromstr(&expected_id, "129183968a65abd6c52da35bff43325001bfc630"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_commit_free(commit); + git_rebase_free(rebase); +} + +int signing_cb_gpg( + git_buf *signature, + git_buf *signature_field, + const char *commit_content, + void *payload) +{ + const char *gpg_signature = "-----BEGIN PGP SIGNATURE-----\n\ +\n\ +iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\ +ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\ +ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\ +7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\ +km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\ +nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\ +jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\ +dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\ +fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\ +cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\ +xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\ +cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\ +=KbsY\n\ +-----END PGP SIGNATURE-----"; + + cl_assert_equal_b(false, git_buf_is_allocated(signature)); + cl_assert_equal_b(false, git_buf_is_allocated(signature_field)); + cl_assert_equal_s(expected_commit_content, commit_content); + cl_assert_equal_p(NULL, payload); + + cl_git_pass(git_buf_set(signature, gpg_signature, strlen(gpg_signature) + 1)); + return GIT_OK; +} + +/* git checkout gravy ; git rebase --merge veal */ +void test_rebase_sign__gpg_with_no_field(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_id; + git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; + git_commit *commit; + const char *expected_commit_raw_header = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ +parent f87d14a4a236582a0278a916340a793714256864\n\ +author Edward Thomson 1405625055 -0400\n\ +committer Rebaser 1405694510 +0000\n\ +gpgsig -----BEGIN PGP SIGNATURE-----\n\ + \n\ + iQIzBAEBCgAdFiEEgVlDEfSlmKn0fvGgK++h5T2/ctIFAlwZcrAACgkQK++h5T2/\n\ + ctIPVhAA42RyZhMdKl5Bm0KtQco2scsukIg2y7tjSwhti91zDu3HQgpusjjo0fQx\n\ + ZzB+OrmlvQ9CDcGpZ0THIzXD8GRJoDMPqdrvZVrBWkGcHvw7/YPA8skzsjkauJ8W\n\ + 7lzF5LCuHSS6OUmPT/+5hEHPin5PB3zhfszyC+Q7aujnIuPJMrKiMnUa+w1HWifM\n\ + km49OOygQ9S6NQoVuEQede22+c76DlDL7yFghGoo1f0sKCE/9LW6SEnwI/bWv9eo\n\ + nom5vOPrvQeJiYCQk+2DyWo8RdSxINtY+G9bPE4RXm+6ZgcXECPm9TYDIWpL36fC\n\ + jvtGLs98woWFElOziBMp5Tb630GMcSI+q5ivHfJ3WS5NKLYLHBNK4iSFN0/dgAnB\n\ + dj6GcKXKWnIBWn6ZM4o40pcM5KSRUUCLtA0ZmjJH4c4zx3X5fUxd+enwkf3e9VZO\n\ + fNKC/+xfq6NfoPUPK9+UnchHpJaJw7RG5tZS+sWCz2xpQ1y3/o49xImNyM3wnpvB\n\ + cRAZabqIHpZa9/DIUkELOtCzln6niqkjRgg3M/YCCNznwV+0RNgz87VtyTPerdef\n\ + xrqn0+ROMF6ebVqIs6PPtuPkxnAJu7TMKXVB5rFnAewS24e6cIGFzeIA7810py3l\n\ + cttVRsdOoego+fiy08eFE+aJIeYiINRGhqOBTsuqG4jIdpdKxPE=\n\ + =KbsY\n\ + -----END PGP SIGNATURE-----\n"; + + rebase_opts.signing_cb = signing_cb_gpg; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + git_oid_fromstr(&expected_id, "bf78348e45c8286f52b760f1db15cb6da030f2ef"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_commit_free(commit); + git_rebase_free(rebase); +} + + +int signing_cb_magic_field( + git_buf *signature, + git_buf *signature_field, + const char *commit_content, + void *payload) +{ + const char *signature_content = "magic word: pretty please"; + const char *signature_field_content = "magicsig"; + + cl_assert_equal_b(false, git_buf_is_allocated(signature)); + cl_assert_equal_b(false, git_buf_is_allocated(signature_field)); + cl_assert_equal_s(expected_commit_content, commit_content); + cl_assert_equal_p(NULL, payload); + + cl_git_pass(git_buf_set(signature, signature_content, + strlen(signature_content) + 1)); + cl_git_pass(git_buf_set(signature_field, signature_field_content, + strlen(signature_field_content) + 1)); + + return GIT_OK; +} + +/* git checkout gravy ; git rebase --merge veal */ +void test_rebase_sign__custom_signature_field(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_id; + git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT; + git_commit *commit; + const char *expected_commit_raw_header = "tree cd99b26250099fc38d30bfaed7797a7275ed3366\n\ +parent f87d14a4a236582a0278a916340a793714256864\n\ +author Edward Thomson 1405625055 -0400\n\ +committer Rebaser 1405694510 +0000\n\ +magicsig magic word: pretty please\n"; + + rebase_opts.signing_cb = signing_cb_magic_field; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &rebase_opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, NULL, NULL)); + + git_oid_fromstr(&expected_id, "f46a4a8d26ae411b02aa61b7d69576627f4a1e1c"); + cl_assert_equal_oid(&expected_id, &commit_id); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_s(expected_commit_raw_header, git_commit_raw_header(commit)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_commit_free(commit); + git_rebase_free(rebase); +} diff --git a/tests/refs/basic.c b/tests/refs/basic.c new file mode 100644 index 000000000..ed0c0bde6 --- /dev/null +++ b/tests/refs/basic.c @@ -0,0 +1,44 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "refs.h" +#include "ref_helpers.h" + +static git_repository *g_repo; + +static const char *loose_tag_ref_name = "refs/tags/e90810b"; + +void test_refs_basic__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_set_ident(g_repo, "me", "foo@example.com")); +} + +void test_refs_basic__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_basic__reference_realloc(void) +{ + git_reference *ref; + git_reference *new_ref; + const char *new_name = "refs/tags/awful/name-which-is/clearly/really-that-much/longer-than/the-old-one"; + + /* Retrieval of the reference to rename */ + cl_git_pass(git_reference_lookup(&ref, g_repo, loose_tag_ref_name)); + + new_ref = git_reference__realloc(&ref, new_name); + cl_assert(new_ref != NULL); + git_reference_free(new_ref); + git_reference_free(ref); + + /* Reload, so we restore the value */ + cl_git_pass(git_reference_lookup(&ref, g_repo, loose_tag_ref_name)); + + cl_git_pass(git_reference_rename(&new_ref, ref, new_name, 1, "log message")); + cl_assert(ref != NULL); + cl_assert(new_ref != NULL); + git_reference_free(new_ref); + git_reference_free(ref); +} diff --git a/tests/refs/branches/checkedout.c b/tests/refs/branches/checkedout.c new file mode 100644 index 000000000..d6dab2c0e --- /dev/null +++ b/tests/refs/branches/checkedout.c @@ -0,0 +1,53 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "worktree/worktree_helpers.h" + +static git_repository *repo; + +static void assert_checked_out(git_repository *repo, const char *branch, int checked_out) +{ + git_reference *ref; + + cl_git_pass(git_reference_lookup(&ref, repo, branch)); + cl_assert(git_branch_is_checked_out(ref) == checked_out); + + git_reference_free(ref); +} + +void test_refs_branches_checkedout__simple_repo(void) +{ + repo = cl_git_sandbox_init("testrepo"); + assert_checked_out(repo, "refs/heads/master", 1); + assert_checked_out(repo, "refs/heads/executable", 0); + cl_git_sandbox_cleanup(); +} + +void test_refs_branches_checkedout__worktree(void) +{ + static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT("testrepo", "testrepo-worktree"); + + setup_fixture_worktree(&fixture); + + assert_checked_out(fixture.repo, "refs/heads/master", 1); + assert_checked_out(fixture.repo, "refs/heads/testrepo-worktree", 1); + + assert_checked_out(fixture.worktree, "refs/heads/master", 1); + assert_checked_out(fixture.worktree, "refs/heads/testrepo-worktree", 1); + + cleanup_fixture_worktree(&fixture); +} + +void test_refs_branches_checkedout__head_is_not_checked_out(void) +{ + repo = cl_git_sandbox_init("testrepo"); + assert_checked_out(repo, "HEAD", 0); + cl_git_sandbox_cleanup(); +} + +void test_refs_branches_checkedout__master_in_bare_repo_is_not_checked_out(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + assert_checked_out(repo, "refs/heads/master", 0); + cl_git_sandbox_cleanup(); +} diff --git a/tests/refs/branches/delete.c b/tests/refs/branches/delete.c index 553d80033..6093c7886 100644 --- a/tests/refs/branches/delete.c +++ b/tests/refs/branches/delete.c @@ -2,7 +2,7 @@ #include "refs.h" #include "repo/repo_helpers.h" #include "config/config_helpers.h" -#include "fileops.h" +#include "futils.h" #include "reflog.h" static git_repository *repo; diff --git a/tests/refs/branches/lookup.c b/tests/refs/branches/lookup.c index 95d49a4b3..ef0c1f97d 100644 --- a/tests/refs/branches/lookup.c +++ b/tests/refs/branches/lookup.c @@ -20,20 +20,41 @@ void test_refs_branches_lookup__cleanup(void) repo = NULL; } -void test_refs_branches_lookup__can_retrieve_a_local_branch(void) +void test_refs_branches_lookup__can_retrieve_a_local_branch_local(void) { cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); } -void test_refs_branches_lookup__can_retrieve_a_remote_tracking_branch(void) +void test_refs_branches_lookup__can_retrieve_a_local_branch_all(void) +{ + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_ALL)); +} + +void test_refs_branches_lookup__trying_to_retrieve_a_local_branch_remote(void) +{ + cl_git_fail(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_REMOTE)); +} + +void test_refs_branches_lookup__can_retrieve_a_remote_tracking_branch_remote(void) { cl_git_pass(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_REMOTE)); } +void test_refs_branches_lookup__can_retrieve_a_remote_tracking_branch_all(void) +{ + cl_git_pass(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_ALL)); +} + +void test_refs_branches_lookup__trying_to_retrieve_a_remote_tracking_branch_local(void) +{ + cl_git_fail(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_LOCAL)); +} + void test_refs_branches_lookup__trying_to_retrieve_an_unknown_branch_returns_ENOTFOUND(void) { cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "where/are/you", GIT_BRANCH_LOCAL)); cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "over/here", GIT_BRANCH_REMOTE)); + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "maybe/here", GIT_BRANCH_ALL)); } void test_refs_branches_lookup__trying_to_retrieve_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void) @@ -42,4 +63,6 @@ void test_refs_branches_lookup__trying_to_retrieve_a_branch_with_an_invalid_name git_branch_lookup(&branch, repo, "are/you/inv@{id", GIT_BRANCH_LOCAL)); cl_assert_equal_i(GIT_EINVALIDSPEC, git_branch_lookup(&branch, repo, "yes/i am", GIT_BRANCH_REMOTE)); + cl_assert_equal_i(GIT_EINVALIDSPEC, + git_branch_lookup(&branch, repo, "inv al/id", GIT_BRANCH_ALL)); } diff --git a/tests/refs/delete.c b/tests/refs/delete.c index 4cc78aa9c..3e99a7959 100644 --- a/tests/refs/delete.c +++ b/tests/refs/delete.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "git2/reflog.h" #include "git2/refdb.h" #include "reflog.h" @@ -105,3 +105,14 @@ void test_refs_delete__remove(void) cl_git_fail(git_reference_lookup(&ref, g_repo, packed_test_head_name)); } + +void test_refs_delete__head(void) +{ + git_reference *ref; + + /* Check that it is not possible to delete HEAD */ + + cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); + cl_git_fail(git_reference_delete(ref)); + git_reference_free(ref); +} diff --git a/tests/refs/foreachglob.c b/tests/refs/foreachglob.c index a13529376..3ff18a27a 100644 --- a/tests/refs/foreachglob.c +++ b/tests/refs/foreachglob.c @@ -48,8 +48,8 @@ static void assert_retrieval(const char *glob, int expected_count) void test_refs_foreachglob__retrieve_all_refs(void) { - /* 12 heads (including one packed head) + 1 note + 2 remotes + 7 tags */ - assert_retrieval("*", 22); + /* 12 heads (including one packed head) + 1 note + 2 remotes + 7 tags + 1 blob */ + assert_retrieval("*", 23); } void test_refs_foreachglob__retrieve_remote_branches(void) diff --git a/tests/refs/iterator.c b/tests/refs/iterator.c index 18e9d1d5b..8d52755c2 100644 --- a/tests/refs/iterator.c +++ b/tests/refs/iterator.c @@ -15,6 +15,7 @@ void test_refs_iterator__cleanup(void) } static const char *refnames[] = { + "refs/blobs/annotated_tag_to_blob", "refs/heads/br2", "refs/heads/cannot-fetch", "refs/heads/chomped", @@ -40,6 +41,7 @@ static const char *refnames[] = { }; static const char *refnames_with_symlink[] = { + "refs/blobs/annotated_tag_to_blob", "refs/heads/br2", "refs/heads/cannot-fetch", "refs/heads/chomped", @@ -99,7 +101,7 @@ void test_refs_iterator__list(void) git_vector output; git_reference *ref; - cl_git_pass(git_vector_init(&output, 32, &refcmp_cb)); + cl_git_pass(git_vector_init(&output, 33, &refcmp_cb)); cl_git_pass(git_reference_iterator_new(&iter, repo)); while (1) { @@ -143,7 +145,7 @@ static int refs_foreach_cb(git_reference *reference, void *payload) void test_refs_iterator__foreach(void) { git_vector output; - cl_git_pass(git_vector_init(&output, 32, &refcmp_cb)); + cl_git_pass(git_vector_init(&output, 33, &refcmp_cb)); cl_git_pass(git_reference_foreach(repo, refs_foreach_cb, &output)); assert_all_refnames_match(refnames, &output); } diff --git a/tests/refs/list.c b/tests/refs/list.c index 3e8c82c94..725d38161 100644 --- a/tests/refs/list.c +++ b/tests/refs/list.c @@ -36,7 +36,7 @@ void test_refs_list__all(void) /* We have exactly 12 refs in total if we include the packed ones: * there is a reference that exists both in the packfile and as * loose, but we only list it once */ - cl_assert_equal_i((int)ref_list.count, 18); + cl_assert_equal_i((int)ref_list.count, 19); git_strarray_free(&ref_list); } @@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten "144344043ba4d4a405da03de3844aa829ae8be0e\n"); cl_git_pass(git_reference_list(&ref_list, g_repo)); - cl_assert_equal_i((int)ref_list.count, 18); + cl_assert_equal_i((int)ref_list.count, 19); git_strarray_free(&ref_list); } diff --git a/tests/refs/normalize.c b/tests/refs/normalize.c index 6da005da0..ff815002c 100644 --- a/tests/refs/normalize.c +++ b/tests/refs/normalize.c @@ -352,12 +352,12 @@ void test_refs_normalize__buffer_has_to_be_big_enough_to_hold_the_normalized_ver void test_refs_normalize__refspec_pattern(void) { - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/*foo/bar"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/foo*/bar"); - ensure_refname_invalid( - GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/f*o/bar"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/*foo/bar", "heads/*foo/bar"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/foo*/bar", "heads/foo*/bar"); + ensure_refname_normalized( + GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "heads/f*o/bar", "heads/f*o/bar"); ensure_refname_invalid( GIT_REFERENCE_FORMAT_REFSPEC_PATTERN, "foo"); diff --git a/tests/refs/pack.c b/tests/refs/pack.c index 92312e26d..676fb1759 100644 --- a/tests/refs/pack.c +++ b/tests/refs/pack.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "git2/reflog.h" #include "git2/refdb.h" #include "reflog.h" diff --git a/tests/refs/races.c b/tests/refs/races.c index fbecf4a75..04a1bc17b 100644 --- a/tests/refs/races.c +++ b/tests/refs/races.c @@ -74,8 +74,8 @@ void test_refs_races__delete(void) git_reference_free(ref); /* We cannot delete a symbolic value that doesn't match */ - cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); - cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "HEAD", other_refname, 1, NULL, refname)); + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref")); + cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "refs/symref", other_refname, 1, NULL, refname)); cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref)); git_reference_free(ref); @@ -131,19 +131,19 @@ void test_refs_races__switch_symbolic_to_oid(void) git_oid_fromstr(&other_id, other_commit_id); /* Removing a symbolic ref when it's currently direct should fail */ - cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); - cl_git_pass(git_reference_create(&ref2, g_repo, "HEAD", &id, 1, NULL)); + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref")); + cl_git_pass(git_reference_create(&ref2, g_repo, "refs/symref", &id, 1, NULL)); cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref)); git_reference_free(ref); git_reference_free(ref2); - cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "HEAD", refname, 1, NULL)); + cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "refs/symref", refname, 1, NULL)); git_reference_free(ref); /* Updating a symbolic ref when it's currently direct should fail */ - cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); - cl_git_pass(git_reference_create(&ref2, g_repo, "HEAD", &id, 1, NULL)); + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref")); + cl_git_pass(git_reference_create(&ref2, g_repo, "refs/symref", &id, 1, NULL)); cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_set_target(&ref3, ref, other_refname, NULL)); git_reference_free(ref); diff --git a/tests/refs/reflog/messages.c b/tests/refs/reflog/messages.c index 5ca9ab31b..43f59a84b 100644 --- a/tests/refs/reflog/messages.c +++ b/tests/refs/reflog/messages.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "git2/reflog.h" #include "reflog.h" #include "refs.h" @@ -281,6 +281,27 @@ void test_refs_reflog_messages__creating_a_direct_reference(void) git_reference_free(reference); } +void test_refs_reflog_messages__newline_gets_replaced(void) +{ + const git_reflog_entry *entry; + git_signature *signature; + git_reflog *reflog; + git_oid oid; + + cl_git_pass(git_signature_now(&signature, "me", "foo@example.com")); + cl_git_pass(git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + + cl_git_pass(git_reflog_read(&reflog, g_repo, "HEAD")); + cl_assert_equal_sz(7, git_reflog_entrycount(reflog)); + cl_git_pass(git_reflog_append(reflog, &oid, signature, "inner\nnewline")); + cl_assert_equal_sz(8, git_reflog_entrycount(reflog)); + + cl_assert(entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_s(git_reflog_entry_message(entry), "inner newline"); + + git_signature_free(signature); + git_reflog_free(reflog); +} void test_refs_reflog_messages__renaming_ref(void) { diff --git a/tests/refs/reflog/reflog.c b/tests/refs/reflog/reflog.c index cf8c5c2d3..5cefc3227 100644 --- a/tests/refs/reflog/reflog.c +++ b/tests/refs/reflog/reflog.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "git2/reflog.h" #include "reflog.h" @@ -87,15 +87,13 @@ void test_refs_reflog_reflog__append_then_read(void) cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); cl_git_pass(git_reflog_read(&reflog, g_repo, new_ref)); - - cl_git_fail(git_reflog_append(reflog, &oid, committer, "no inner\nnewline")); cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL)); cl_git_pass(git_reflog_append(reflog, &oid, committer, commit_msg "\n")); cl_git_pass(git_reflog_write(reflog)); - git_reflog_free(reflog); assert_appends(committer, &oid); + git_reflog_free(reflog); git_signature_free(committer); } @@ -224,45 +222,45 @@ void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_re git_buf_dispose(&subtrees_log_path); } -void test_refs_reflog_reflog__reading_a_reflog_with_invalid_format_returns_error(void) +void test_refs_reflog_reflog__reading_a_reflog_with_invalid_format_succeeds(void) { git_reflog *reflog; - const git_error *error; const char *refname = "refs/heads/newline"; const char *refmessage = "Reflog*message with a newline and enough content after it to pass the GIT_REFLOG_SIZE_MIN check inside reflog_parse."; + const git_reflog_entry *entry; git_reference *ref; git_oid id; git_buf logpath = GIT_BUF_INIT, logcontents = GIT_BUF_INIT; char *star; - git_oid_fromstr(&id, current_master_tip); - - /* create a new branch */ + /* Create a new branch. */ + cl_git_pass(git_oid_fromstr(&id, current_master_tip)); cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, refmessage)); - /* corrupt the branch reflog by introducing a newline inside the reflog message (we replace '*' with '\n') */ - git_buf_join_n(&logpath, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname); + /* + * Corrupt the branch reflog by introducing a newline inside the reflog message. + * We do this by replacing '*' with '\n' + */ + cl_git_pass(git_buf_join_n(&logpath, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname)); cl_git_pass(git_futils_readbuffer(&logcontents, git_buf_cstr(&logpath))); cl_assert((star = strchr(git_buf_cstr(&logcontents), '*')) != NULL); *star = '\n'; cl_git_rewritefile(git_buf_cstr(&logpath), git_buf_cstr(&logcontents)); - /* confirm that the file was rewritten successfully and now contains a '\n' in the expected location */ + /* + * Confirm that the file was rewritten successfully + * and now contains a '\n' in the expected location + */ cl_git_pass(git_futils_readbuffer(&logcontents, git_buf_cstr(&logpath))); cl_assert(strstr(git_buf_cstr(&logcontents), "Reflog\nmessage") != NULL); - /* clear the error state so we can capture the error generated by git_reflog_read */ - git_error_clear(); - - cl_git_fail(git_reflog_read(&reflog, g_repo, refname)); - - error = git_error_last(); - - cl_assert(error != NULL); - cl_assert_equal_s("unable to parse OID - contains invalid characters", error->message); + cl_git_pass(git_reflog_read(&reflog, g_repo, refname)); + cl_assert(entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_s(git_reflog_entry_message(entry), "Reflog"); git_reference_free(ref); + git_reflog_free(reflog); git_buf_dispose(&logpath); git_buf_dispose(&logcontents); } diff --git a/tests/refs/rename.c b/tests/refs/rename.c index 9933bee1d..b1b75cd64 100644 --- a/tests/refs/rename.c +++ b/tests/refs/rename.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "git2/reflog.h" #include "reflog.h" #include "refs.h" diff --git a/tests/refs/transactions.c b/tests/refs/transactions.c index 39ea1cae5..50c102ad0 100644 --- a/tests/refs/transactions.c +++ b/tests/refs/transactions.c @@ -108,3 +108,50 @@ void test_refs_transactions__unlocked_set(void) cl_git_fail_with(GIT_ENOTFOUND, git_transaction_set_target(g_tx, "refs/heads/foo", &id, NULL, NULL)); cl_git_pass(git_transaction_commit(g_tx)); } + +void test_refs_transactions__error_on_locking_locked_ref(void) +{ + git_oid id; + git_transaction *g_tx_with_lock; + git_repository *g_repo_with_locking_tx; + const char *g_repo_path = git_repository_path(g_repo); + + /* prepare a separate transaction in another instance of testrepo and lock master */ + cl_git_pass(git_repository_open(&g_repo_with_locking_tx, g_repo_path)); + cl_git_pass(git_transaction_new(&g_tx_with_lock, g_repo_with_locking_tx)); + cl_git_pass(git_transaction_lock_ref(g_tx_with_lock, "refs/heads/master")); + + /* lock reference for set_target */ + cl_git_pass(git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + cl_git_fail_with(GIT_ELOCKED, git_transaction_lock_ref(g_tx, "refs/heads/master")); + cl_git_fail_with(GIT_ENOTFOUND, git_transaction_set_target(g_tx, "refs/heads/master", &id, NULL, NULL)); + + git_transaction_free(g_tx_with_lock); + git_repository_free(g_repo_with_locking_tx); +} + +void test_refs_transactions__commit_unlocks_unmodified_ref(void) +{ + git_transaction *second_tx; + + cl_git_pass(git_transaction_new(&second_tx, g_repo)); + cl_git_pass(git_transaction_lock_ref(second_tx, "refs/heads/master")); + cl_git_pass(git_transaction_commit(second_tx)); + + /* a transaction must now be able to get the lock */ + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); + + git_transaction_free(second_tx); +} + +void test_refs_transactions__free_unlocks_unmodified_ref(void) +{ + git_transaction *second_tx; + + cl_git_pass(git_transaction_new(&second_tx, g_repo)); + cl_git_pass(git_transaction_lock_ref(second_tx, "refs/heads/master")); + git_transaction_free(second_tx); + + /* a transaction must now be able to get the lock */ + cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master")); +} diff --git a/tests/remote/list.c b/tests/remote/list.c new file mode 100644 index 000000000..5abb95106 --- /dev/null +++ b/tests/remote/list.c @@ -0,0 +1,43 @@ +#include "clar_libgit2.h" +#include "config/config_helpers.h" + +static git_repository *_repo; + +#define TEST_URL "http://github.com/libgit2/libgit2.git" + +void test_remote_list__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo"); +} + +void test_remote_list__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_remote_list__always_checks_disk_config(void) +{ + git_repository *repo; + git_strarray remotes; + git_remote *remote; + + cl_git_pass(git_repository_open(&repo, git_repository_path(_repo))); + + cl_git_pass(git_remote_list(&remotes, _repo)); + cl_assert_equal_sz(remotes.count, 1); + git_strarray_free(&remotes); + + cl_git_pass(git_remote_create(&remote, _repo, "valid-name", TEST_URL)); + + cl_git_pass(git_remote_list(&remotes, _repo)); + cl_assert_equal_sz(remotes.count, 2); + git_strarray_free(&remotes); + + cl_git_pass(git_remote_list(&remotes, repo)); + cl_assert_equal_sz(remotes.count, 2); + git_strarray_free(&remotes); + + git_repository_free(repo); + git_remote_free(remote); +} + diff --git a/tests/repo/config.c b/tests/repo/config.c index b92d84202..6ca31f550 100644 --- a/tests/repo/config.c +++ b/tests/repo/config.c @@ -1,6 +1,6 @@ #include "clar_libgit2.h" #include "sysdir.h" -#include "fileops.h" +#include "futils.h" #include static git_buf path = GIT_BUF_INIT; @@ -104,9 +104,9 @@ void test_repo_config__read_with_no_configs_at_all(void) cl_assert(!git_path_isfile("empty_standard_repo/.git/config")); cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__cvar_cache_clear(repo); + git_repository__configmap_lookup_cache_clear(repo); val = -1; - cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV)); + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); cl_assert_equal_i(GIT_ABBREV_DEFAULT, val); git_repository_free(repo); @@ -121,9 +121,9 @@ void test_repo_config__read_with_no_configs_at_all(void) GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__cvar_cache_clear(repo); + git_repository__configmap_lookup_cache_clear(repo); val = -1; - cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV)); + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); cl_assert_equal_i(10, val); git_repository_free(repo); @@ -136,9 +136,9 @@ void test_repo_config__read_with_no_configs_at_all(void) GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__cvar_cache_clear(repo); + git_repository__configmap_lookup_cache_clear(repo); val = -1; - cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV)); + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); cl_assert_equal_i(20, val); git_repository_free(repo); @@ -151,9 +151,9 @@ void test_repo_config__read_with_no_configs_at_all(void) GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__cvar_cache_clear(repo); + git_repository__configmap_lookup_cache_clear(repo); val = -1; - cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV)); + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); cl_assert_equal_i(30, val); git_repository_free(repo); @@ -162,18 +162,18 @@ void test_repo_config__read_with_no_configs_at_all(void) cl_git_rewritefile("empty_standard_repo/.git/config", "[core]\n\tabbrev = 40\n"); cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__cvar_cache_clear(repo); + git_repository__configmap_lookup_cache_clear(repo); val = -1; - cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV)); + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); cl_assert_equal_i(40, val); git_repository_free(repo); /* with all configs but delete the files ? */ cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__cvar_cache_clear(repo); + git_repository__configmap_lookup_cache_clear(repo); val = -1; - cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV)); + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); cl_assert_equal_i(40, val); cl_must_pass(p_unlink("empty_standard_repo/.git/config")); @@ -188,9 +188,9 @@ void test_repo_config__read_with_no_configs_at_all(void) cl_must_pass(p_unlink("alternate/3/.gitconfig")); cl_assert(!git_path_isfile("alternate/3/.gitconfig")); - git_repository__cvar_cache_clear(repo); + git_repository__configmap_lookup_cache_clear(repo); val = -1; - cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV)); + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); cl_assert_equal_i(40, val); git_repository_free(repo); @@ -200,9 +200,9 @@ void test_repo_config__read_with_no_configs_at_all(void) cl_assert(!git_path_isfile("alternate/3/.gitconfig")); cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); - git_repository__cvar_cache_clear(repo); + git_repository__configmap_lookup_cache_clear(repo); val = -1; - cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV)); + cl_git_pass(git_repository__configmap_lookup(&val, repo, GIT_CONFIGMAP_ABBREV)); cl_assert_equal_i(7, val); git_repository_free(repo); diff --git a/tests/repo/discover.c b/tests/repo/discover.c index cc61c7125..c026eefc3 100644 --- a/tests/repo/discover.c +++ b/tests/repo/discover.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "odb.h" -#include "fileops.h" +#include "futils.h" #include "repository.h" #define TEMP_REPO_FOLDER "temprepo/" diff --git a/tests/repo/env.c b/tests/repo/env.c index 9dafda198..024661692 100644 --- a/tests/repo/env.c +++ b/tests/repo/env.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "sysdir.h" #include diff --git a/tests/repo/init.c b/tests/repo/init.c index 6e6e65297..5a95229e6 100644 --- a/tests/repo/init.c +++ b/tests/repo/init.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "repository.h" #include "config.h" #include "path.h" @@ -13,20 +13,13 @@ enum repo_mode { static git_repository *_repo = NULL; static git_buf _global_path = GIT_BUF_INIT; -static mode_t g_umask = 0; void test_repo_init__initialize(void) { _repo = NULL; - /* load umask if not already loaded */ - if (!g_umask) { - g_umask = p_umask(022); - (void)p_umask(g_umask); - } - - git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, - &_global_path); + git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, + &_global_path); } void test_repo_init__cleanup(void) @@ -260,7 +253,7 @@ void test_repo_init__symlinks_win32_enabled_by_global_config(void) git_config *config, *repo_config; int val; - if (!filesystem_supports_symlinks("link")) + if (!git_path_supports_symlinks("link")) cl_skip(); create_tmp_global_config("tmp_global_config", "core.symlinks", "true"); @@ -303,7 +296,7 @@ void test_repo_init__symlinks_posix_detected(void) cl_skip(); #else assert_config_entry_on_init( - "core.symlinks", filesystem_supports_symlinks("link") ? GIT_ENOTFOUND : false); + "core.symlinks", git_path_supports_symlinks("link") ? GIT_ENOTFOUND : false); #endif } @@ -380,17 +373,6 @@ void test_repo_init__sets_logAllRefUpdates_according_to_type_of_repository(void) assert_config_entry_on_init_bytype("core.logallrefupdates", true, false); } -void test_repo_init__empty_template_path(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - opts.template_path = ""; - - cl_git_pass(git_futils_mkdir("foo", 0755, 0)); - cl_git_pass(git_repository_init_ext(&_repo, "foo", &opts)); - - cleanup_repository("foo"); -} - void test_repo_init__extended_0(void) { git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; @@ -524,253 +506,6 @@ void test_repo_init__relative_gitdir_2(void) cleanup_repository("root"); } -#define CLEAR_FOR_CORE_FILEMODE(M) ((M) &= ~0177) - -static void assert_hooks_match( - const char *template_dir, - const char *repo_dir, - const char *hook_path, - bool core_filemode) -{ - git_buf expected = GIT_BUF_INIT; - git_buf actual = GIT_BUF_INIT; - struct stat expected_st, st; - - cl_git_pass(git_buf_joinpath(&expected, template_dir, hook_path)); - cl_git_pass(git_path_lstat(expected.ptr, &expected_st)); - - cl_git_pass(git_buf_joinpath(&actual, repo_dir, hook_path)); - cl_git_pass(git_path_lstat(actual.ptr, &st)); - - cl_assert(expected_st.st_size == st.st_size); - - if (GIT_MODE_TYPE(expected_st.st_mode) != GIT_FILEMODE_LINK) { - mode_t expected_mode = - GIT_MODE_TYPE(expected_st.st_mode) | - (GIT_PERMS_FOR_WRITE(expected_st.st_mode) & ~g_umask); - - if (!core_filemode) { - CLEAR_FOR_CORE_FILEMODE(expected_mode); - CLEAR_FOR_CORE_FILEMODE(st.st_mode); - } - - cl_assert_equal_i_fmt(expected_mode, st.st_mode, "%07o"); - } - - git_buf_dispose(&expected); - git_buf_dispose(&actual); -} - -static void assert_mode_seems_okay( - const char *base, const char *path, - git_filemode_t expect_mode, bool expect_setgid, bool core_filemode) -{ - git_buf full = GIT_BUF_INIT; - struct stat st; - - cl_git_pass(git_buf_joinpath(&full, base, path)); - cl_git_pass(git_path_lstat(full.ptr, &st)); - git_buf_dispose(&full); - - if (!core_filemode) { - CLEAR_FOR_CORE_FILEMODE(expect_mode); - CLEAR_FOR_CORE_FILEMODE(st.st_mode); - expect_setgid = false; - } - - if (S_ISGID != 0) - cl_assert_equal_b(expect_setgid, (st.st_mode & S_ISGID) != 0); - - cl_assert_equal_b( - GIT_PERMS_IS_EXEC(expect_mode), GIT_PERMS_IS_EXEC(st.st_mode)); - - cl_assert_equal_i_fmt( - GIT_MODE_TYPE(expect_mode), GIT_MODE_TYPE(st.st_mode), "%07o"); -} - -static const char *template_sandbox(const char *name) -{ - git_buf hooks_path = GIT_BUF_INIT, link_path = GIT_BUF_INIT, - dotfile_path = GIT_BUF_INIT; - const char *path = cl_fixture(name); - - cl_fixture_sandbox(name); - - /* create a symlink from link.sample to update.sample if the filesystem - * supports it. - */ - - cl_git_pass(git_buf_joinpath(&hooks_path, name, "hooks")); - cl_git_pass(git_buf_joinpath(&link_path, hooks_path.ptr, "link.sample")); - -#ifdef GIT_WIN32 - cl_git_mkfile(link_path.ptr, "#!/bin/sh\necho hello, world\n"); -#else - cl_must_pass(symlink("update.sample", link_path.ptr)); -#endif - - /* create a file starting with a dot */ - cl_git_pass(git_buf_joinpath(&dotfile_path, hooks_path.ptr, ".dotfile")); - cl_git_mkfile(dotfile_path.ptr, "something\n"); - git_buf_dispose(&dotfile_path); - - git_buf_dispose(&dotfile_path); - git_buf_dispose(&link_path); - git_buf_dispose(&hooks_path); - - return path; -} - -static void configure_templatedir(const char *template_path) -{ - create_tmp_global_config("tmp_global_path", "init.templatedir", template_path); -} - -static void validate_templates(git_repository *repo, const char *template_path) -{ - git_buf template_description = GIT_BUF_INIT; - git_buf repo_description = GIT_BUF_INIT; - git_buf expected = GIT_BUF_INIT; - git_buf actual = GIT_BUF_INIT; - int filemode; - - cl_git_pass(git_buf_joinpath(&template_description, template_path, - "description")); - cl_git_pass(git_buf_joinpath(&repo_description, git_repository_path(repo), - "description")); - - cl_git_pass(git_futils_readbuffer(&expected, template_description.ptr)); - cl_git_pass(git_futils_readbuffer(&actual, repo_description.ptr)); - - cl_assert_equal_s(expected.ptr, actual.ptr); - - filemode = cl_repo_get_bool(repo, "core.filemode"); - - assert_hooks_match( - template_path, git_repository_path(repo), - "hooks/update.sample", filemode); - - assert_hooks_match( - template_path, git_repository_path(repo), - "hooks/link.sample", filemode); - - assert_hooks_match( - template_path, git_repository_path(repo), - "hooks/.dotfile", filemode); - - git_buf_dispose(&expected); - git_buf_dispose(&actual); - git_buf_dispose(&repo_description); - git_buf_dispose(&template_description); -} - -void test_repo_init__external_templates_specified_in_options(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - cl_set_cleanup(&cleanup_repository, "templated.git"); - template_sandbox("template"); - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - opts.template_path = "template"; - - cl_git_pass(git_repository_init_ext(&_repo, "templated.git", &opts)); - - cl_assert(git_repository_is_bare(_repo)); - - cl_assert(!git__suffixcmp(git_repository_path(_repo), "/templated.git/")); - - validate_templates(_repo, "template"); - cl_fixture_cleanup("template"); -} - -void test_repo_init__external_templates_specified_in_config(void) -{ - git_buf template_path = GIT_BUF_INIT; - - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - cl_set_cleanup(&cleanup_repository, "templated.git"); - template_sandbox("template"); - - cl_git_pass(git_buf_joinpath(&template_path, clar_sandbox_path(), - "template")); - - configure_templatedir(template_path.ptr); - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - - cl_git_pass(git_repository_init_ext(&_repo, "templated.git", &opts)); - - validate_templates(_repo, "template"); - cl_fixture_cleanup("template"); - - git_buf_dispose(&template_path); -} - -void test_repo_init__external_templates_with_leading_dot(void) -{ - git_buf template_path = GIT_BUF_INIT; - - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - - cl_set_cleanup(&cleanup_repository, "templated.git"); - template_sandbox("template"); - - cl_must_pass(p_rename("template", ".template_with_leading_dot")); - - cl_git_pass(git_buf_joinpath(&template_path, clar_sandbox_path(), - ".template_with_leading_dot")); - - configure_templatedir(template_path.ptr); - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - - cl_git_pass(git_repository_init_ext(&_repo, "templated.git", &opts)); - - validate_templates(_repo, ".template_with_leading_dot"); - cl_fixture_cleanup(".template_with_leading_dot"); - - git_buf_dispose(&template_path); -} - -void test_repo_init__extended_with_template_and_shared_mode(void) -{ - git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - int filemode = true; - const char *repo_path = NULL; - - cl_set_cleanup(&cleanup_repository, "init_shared_from_tpl"); - template_sandbox("template"); - - opts.flags = GIT_REPOSITORY_INIT_MKPATH | - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; - opts.template_path = "template"; - opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP; - - cl_git_pass(git_repository_init_ext(&_repo, "init_shared_from_tpl", &opts)); - - cl_assert(!git_repository_is_bare(_repo)); - cl_assert(!git__suffixcmp(git_repository_path(_repo), "/init_shared_from_tpl/.git/")); - - filemode = cl_repo_get_bool(_repo, "core.filemode"); - - repo_path = git_repository_path(_repo); - assert_mode_seems_okay(repo_path, "hooks", - GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode); - assert_mode_seems_okay(repo_path, "info", - GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode); - assert_mode_seems_okay(repo_path, "description", - GIT_FILEMODE_BLOB, false, filemode); - - validate_templates(_repo, "template"); - - cl_fixture_cleanup("template"); -} - void test_repo_init__can_reinit_an_initialized_repository(void) { git_repository *reinit; @@ -878,14 +613,55 @@ void test_repo_init__at_filesystem_root(void) git_repository_free(repo); } -void test_repo_init__nonexistent_paths(void) +void test_repo_init__nonexisting_directory(void) { + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; git_repository *repo; + /* + * If creating a repo with non-existing parent directories, then libgit2 + * will by default create the complete directory hierarchy if using + * `git_repository_init`. Thus, let's use the extended version and not + * set the `GIT_REPOSITORY_INIT_MKPATH` flag. + */ + cl_git_fail(git_repository_init_ext(&repo, "nonexisting/path", &opts)); +} + +void test_repo_init__nonexisting_root(void) +{ #ifdef GIT_WIN32 + git_repository *repo; + + /* + * This really only depends on the nonexistence of the Q: drive. We + * cannot implement the equivalent test on Unix systems, as there is + * fundamentally no path that is disconnected from the root directory. + */ cl_git_fail(git_repository_init(&repo, "Q:/non/existent/path", 0)); cl_git_fail(git_repository_init(&repo, "Q:\\non\\existent\\path", 0)); #else - cl_git_fail(git_repository_init(&repo, "/non/existent/path", 0)); + clar__skip(); +#endif +} + +void test_repo_init__unwriteable_directory(void) +{ +#ifndef GIT_WIN32 + git_repository *repo; + + if (geteuid() == 0) + clar__skip(); + + /* + * Create a non-writeable directory so that we cannot create directories + * inside of it. The root user has CAP_DAC_OVERRIDE, so he doesn't care + * for the directory permissions and thus we need to skip the test if + * run as root user. + */ + cl_must_pass(p_mkdir("unwriteable", 0444)); + cl_git_fail(git_repository_init(&repo, "unwriteable/repo", 0)); + cl_must_pass(p_rmdir("unwriteable")); +#else + clar__skip(); #endif } diff --git a/tests/repo/open.c b/tests/repo/open.c index 06ec71bfd..448ccdc87 100644 --- a/tests/repo/open.c +++ b/tests/repo/open.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "sysdir.h" #include @@ -88,6 +88,17 @@ void test_repo_open__open_with_discover(void) cl_fixture_cleanup("attr"); } +void test_repo_open__check_if_repository(void) +{ + cl_git_sandbox_init("empty_standard_repo"); + + /* Pass NULL for the output parameter to check for but not open the repo */ + cl_git_pass(git_repository_open_ext(NULL, "empty_standard_repo", 0, NULL)); + cl_git_fail(git_repository_open_ext(NULL, "repo_does_not_exist", 0, NULL)); + + cl_fixture_cleanup("empty_standard_repo"); +} + static void make_gitlink_dir(const char *dir, const char *linktext) { git_buf path = GIT_BUF_INIT; @@ -118,6 +129,36 @@ void test_repo_open__gitlinked(void) git_repository_free(repo2); } +void test_repo_open__with_symlinked_config(void) +{ +#ifndef GIT_WIN32 + git_buf path = GIT_BUF_INIT; + git_repository *repo; + git_config *cfg; + int32_t value; + + cl_git_sandbox_init("empty_standard_repo"); + + /* Setup .gitconfig as symlink */ + cl_git_pass(git_futils_mkdir_r("home", 0777)); + cl_git_mkfile("home/.gitconfig.linked", "[global]\ntest = 4567\n"); + cl_must_pass(symlink(".gitconfig.linked", "home/.gitconfig")); + cl_git_pass(git_path_prettify(&path, "home", NULL)); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); + + cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); + cl_git_pass(git_config_open_default(&cfg)); + cl_git_pass(git_config_get_int32(&value, cfg, "global.test")); + cl_assert_equal_i(4567, value); + + git_config_free(cfg); + git_repository_free(repo); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); + cl_sandbox_set_search_path_defaults(); + git_buf_dispose(&path); +#endif +} + void test_repo_open__from_git_new_workdir(void) { #ifndef GIT_WIN32 @@ -236,7 +277,9 @@ void test_repo_open__bad_gitlinks(void) for (scan = bad_links; *scan != NULL; scan++) { make_gitlink_dir("alternate", *scan); + repo = NULL; cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL)); + cl_assert(repo == NULL); } git_futils_rmdir_r("invalid", NULL, GIT_RMDIR_REMOVE_FILES); diff --git a/tests/repo/repo_helpers.c b/tests/repo/repo_helpers.c index 4256314f1..b22f3f6ba 100644 --- a/tests/repo/repo_helpers.c +++ b/tests/repo/repo_helpers.c @@ -21,21 +21,6 @@ void delete_head(git_repository* repo) git_buf_dispose(&head_path); } -int filesystem_supports_symlinks(const char *path) -{ - struct stat st; - bool support = 0; - - if (p_symlink("target", path) == 0) { - if (p_lstat(path, &st) == 0 && S_ISLNK(st.st_mode)) - support = 1; - - p_unlink(path); - } - - return support; -} - void create_tmp_global_config(const char *dirname, const char *key, const char *val) { git_buf path = GIT_BUF_INIT; diff --git a/tests/repo/repo_helpers.h b/tests/repo/repo_helpers.h index 2c9aeabee..a93bf36ae 100644 --- a/tests/repo/repo_helpers.h +++ b/tests/repo/repo_helpers.h @@ -4,5 +4,4 @@ extern void make_head_unborn(git_repository* repo, const char *target); extern void delete_head(git_repository* repo); -extern int filesystem_supports_symlinks(const char *path); extern void create_tmp_global_config(const char *path, const char *key, const char *val); diff --git a/tests/repo/setters.c b/tests/repo/setters.c index ea6ef12b1..1fac627f6 100644 --- a/tests/repo/setters.c +++ b/tests/repo/setters.c @@ -5,7 +5,7 @@ #include "posix.h" #include "util.h" #include "path.h" -#include "fileops.h" +#include "futils.h" static git_repository *repo; diff --git a/tests/repo/shallow.c b/tests/repo/shallow.c index b9a7b810b..adb7a9e44 100644 --- a/tests/repo/shallow.c +++ b/tests/repo/shallow.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" static git_repository *g_repo; diff --git a/tests/repo/state.c b/tests/repo/state.c index fb8949775..afb361787 100644 --- a/tests/repo/state.c +++ b/tests/repo/state.c @@ -2,7 +2,7 @@ #include "buffer.h" #include "refs.h" #include "posix.h" -#include "fileops.h" +#include "futils.h" static git_repository *_repo; static git_buf _path; diff --git a/tests/repo/template.c b/tests/repo/template.c new file mode 100644 index 000000000..3513190ac --- /dev/null +++ b/tests/repo/template.c @@ -0,0 +1,295 @@ +#include "clar_libgit2.h" + +#include "futils.h" +#include "repo/repo_helpers.h" + +#define CLEAR_FOR_CORE_FILEMODE(M) ((M) &= ~0177) + +static git_repository *_repo = NULL; +static mode_t g_umask = 0; +static git_buf _global_path = GIT_BUF_INIT; + +static const char *fixture_repo; +static const char *fixture_templates; + +void test_repo_template__initialize(void) +{ + _repo = NULL; + + /* load umask if not already loaded */ + if (!g_umask) { + g_umask = p_umask(022); + (void)p_umask(g_umask); + } +} + +void test_repo_template__cleanup(void) +{ + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, + _global_path.ptr); + git_buf_dispose(&_global_path); + + cl_fixture_cleanup("tmp_global_path"); + + if (fixture_repo) { + cl_fixture_cleanup(fixture_repo); + fixture_repo = NULL; + } + + if (fixture_templates) { + cl_fixture_cleanup(fixture_templates); + fixture_templates = NULL; + } + + git_repository_free(_repo); + _repo = NULL; +} + +static void assert_hooks_match( + const char *template_dir, + const char *repo_dir, + const char *hook_path, + bool core_filemode) +{ + git_buf expected = GIT_BUF_INIT; + git_buf actual = GIT_BUF_INIT; + struct stat expected_st, st; + + cl_git_pass(git_buf_joinpath(&expected, template_dir, hook_path)); + cl_git_pass(git_path_lstat(expected.ptr, &expected_st)); + + cl_git_pass(git_buf_joinpath(&actual, repo_dir, hook_path)); + cl_git_pass(git_path_lstat(actual.ptr, &st)); + + cl_assert(expected_st.st_size == st.st_size); + + if (GIT_MODE_TYPE(expected_st.st_mode) != GIT_FILEMODE_LINK) { + mode_t expected_mode = + GIT_MODE_TYPE(expected_st.st_mode) | + (GIT_PERMS_FOR_WRITE(expected_st.st_mode) & ~g_umask); + + if (!core_filemode) { + CLEAR_FOR_CORE_FILEMODE(expected_mode); + CLEAR_FOR_CORE_FILEMODE(st.st_mode); + } + + cl_assert_equal_i_fmt(expected_mode, st.st_mode, "%07o"); + } + + git_buf_dispose(&expected); + git_buf_dispose(&actual); +} + +static void assert_mode_seems_okay( + const char *base, const char *path, + git_filemode_t expect_mode, bool expect_setgid, bool core_filemode) +{ + git_buf full = GIT_BUF_INIT; + struct stat st; + + cl_git_pass(git_buf_joinpath(&full, base, path)); + cl_git_pass(git_path_lstat(full.ptr, &st)); + git_buf_dispose(&full); + + if (!core_filemode) { + CLEAR_FOR_CORE_FILEMODE(expect_mode); + CLEAR_FOR_CORE_FILEMODE(st.st_mode); + expect_setgid = false; + } + + if (S_ISGID != 0) + cl_assert_equal_b(expect_setgid, (st.st_mode & S_ISGID) != 0); + + cl_assert_equal_b( + GIT_PERMS_IS_EXEC(expect_mode), GIT_PERMS_IS_EXEC(st.st_mode)); + + cl_assert_equal_i_fmt( + GIT_MODE_TYPE(expect_mode), GIT_MODE_TYPE(st.st_mode), "%07o"); +} + +static void setup_repo(const char *name, git_repository_init_options *opts) +{ + cl_git_pass(git_repository_init_ext(&_repo, name, opts)); + fixture_repo = name; +} + +static void setup_templates(const char *name, bool setup_globally) +{ + git_buf path = GIT_BUF_INIT; + + cl_fixture_sandbox("template"); + if (strcmp(name, "template")) + cl_must_pass(p_rename("template", name)); + + fixture_templates = name; + + /* + * Create a symlink from link.sample to update.sample if the filesystem + * supports it. + */ + cl_git_pass(git_buf_join3(&path, '/', name, "hooks", "link.sample")); +#ifdef GIT_WIN32 + cl_git_mkfile(path.ptr, "#!/bin/sh\necho hello, world\n"); +#else + cl_must_pass(p_symlink("update.sample", path.ptr)); +#endif + + git_buf_clear(&path); + + /* Create a file starting with a dot */ + cl_git_pass(git_buf_join3(&path, '/', name, "hooks", ".dotfile")); + cl_git_mkfile(path.ptr, "something\n"); + + git_buf_clear(&path); + + if (setup_globally) { + cl_git_pass(git_buf_joinpath(&path, clar_sandbox_path(), name)); + create_tmp_global_config("tmp_global_path", "init.templatedir", path.ptr); + } + + git_buf_dispose(&path); +} + +static void validate_templates(git_repository *repo, const char *template_path) +{ + git_buf path = GIT_BUF_INIT, expected = GIT_BUF_INIT, actual = GIT_BUF_INIT; + int filemode; + + cl_git_pass(git_buf_joinpath(&path, template_path, "description")); + cl_git_pass(git_futils_readbuffer(&expected, path.ptr)); + + git_buf_clear(&path); + + cl_git_pass(git_buf_joinpath(&path, git_repository_path(repo), "description")); + cl_git_pass(git_futils_readbuffer(&actual, path.ptr)); + + cl_assert_equal_s(expected.ptr, actual.ptr); + + filemode = cl_repo_get_bool(repo, "core.filemode"); + + assert_hooks_match( + template_path, git_repository_path(repo), + "hooks/update.sample", filemode); + assert_hooks_match( + template_path, git_repository_path(repo), + "hooks/link.sample", filemode); + assert_hooks_match( + template_path, git_repository_path(repo), + "hooks/.dotfile", filemode); + + git_buf_dispose(&expected); + git_buf_dispose(&actual); + git_buf_dispose(&path); +} + +void test_repo_template__external_templates_specified_in_options(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.template_path = "template"; + + setup_templates("template", false); + setup_repo("templated.git", &opts); + + validate_templates(_repo, "template"); +} + +void test_repo_template__external_templates_specified_in_config(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + + setup_templates("template", true); + setup_repo("templated.git", &opts); + + validate_templates(_repo, "template"); +} + +void test_repo_template__external_templates_with_leading_dot(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + + setup_templates(".template_with_leading_dot", true); + setup_repo("templated.git", &opts); + + validate_templates(_repo, ".template_with_leading_dot"); +} + +void test_repo_template__extended_with_template_and_shared_mode(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + const char *repo_path; + int filemode; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.template_path = "template"; + opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP; + + setup_templates("template", false); + setup_repo("init_shared_from_tpl", &opts); + + filemode = cl_repo_get_bool(_repo, "core.filemode"); + + repo_path = git_repository_path(_repo); + assert_mode_seems_okay(repo_path, "hooks", + GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode); + assert_mode_seems_okay(repo_path, "info", + GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode); + assert_mode_seems_okay(repo_path, "description", + GIT_FILEMODE_BLOB, false, filemode); + + validate_templates(_repo, "template"); +} + +void test_repo_template__templated_head_is_used(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_buf head = GIT_BUF_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + + setup_templates("template", true); + cl_git_mkfile("template/HEAD", "foobar\n"); + setup_repo("repo", &opts); + + cl_git_pass(git_futils_readbuffer(&head, "repo/.git/HEAD")); + cl_assert_equal_s("foobar\n", head.ptr); + + git_buf_dispose(&head); +} + +void test_repo_template__initial_head_option_overrides_template_head(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + git_buf head = GIT_BUF_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.initial_head = "manual"; + + setup_templates("template", true); + cl_git_mkfile("template/HEAD", "foobar\n"); + setup_repo("repo", &opts); + + cl_git_pass(git_futils_readbuffer(&head, "repo/.git/HEAD")); + cl_assert_equal_s("ref: refs/heads/manual\n", head.ptr); + + git_buf_dispose(&head); +} + +void test_repo_template__empty_template_path(void) +{ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.template_path = ""; + + setup_repo("foo", &opts); +} diff --git a/tests/reset/hard.c b/tests/reset/hard.c index b6e91395b..1ea1d13fb 100644 --- a/tests/reset/hard.c +++ b/tests/reset/hard.c @@ -2,7 +2,7 @@ #include "posix.h" #include "reset_helpers.h" #include "path.h" -#include "fileops.h" +#include "futils.h" static git_repository *repo; static git_object *target; diff --git a/tests/resources/attr/attr4 b/tests/resources/attr/attr4 new file mode 100644 index 000000000..fa88df943 Binary files /dev/null and b/tests/resources/attr/attr4 differ diff --git a/tests/resources/blametest.git/objects/83/6bc00b06cb60eb0f629e237ad2b58adb2cfc7e b/tests/resources/blametest.git/objects/83/6bc00b06cb60eb0f629e237ad2b58adb2cfc7e new file mode 100644 index 000000000..71f9c980e Binary files /dev/null and b/tests/resources/blametest.git/objects/83/6bc00b06cb60eb0f629e237ad2b58adb2cfc7e differ diff --git a/tests/resources/blametest.git/objects/a8/ba8436b5d8ccbdfd5be597c194e7bb8e0a092f b/tests/resources/blametest.git/objects/a8/ba8436b5d8ccbdfd5be597c194e7bb8e0a092f new file mode 100644 index 000000000..0bab0ef53 Binary files /dev/null and b/tests/resources/blametest.git/objects/a8/ba8436b5d8ccbdfd5be597c194e7bb8e0a092f differ diff --git a/tests/resources/blametest.git/objects/f9/264f7fbd31ae7a18b7931ed8946fb0aebb0af3 b/tests/resources/blametest.git/objects/f9/264f7fbd31ae7a18b7931ed8946fb0aebb0af3 new file mode 100644 index 000000000..942a7eedc Binary files /dev/null and b/tests/resources/blametest.git/objects/f9/264f7fbd31ae7a18b7931ed8946fb0aebb0af3 differ diff --git a/tests/resources/blametest.git/refs/heads/master b/tests/resources/blametest.git/refs/heads/master index d1bc4ca6b..994877a20 100644 Binary files a/tests/resources/blametest.git/refs/heads/master and b/tests/resources/blametest.git/refs/heads/master differ diff --git a/tests/resources/config/config-oom b/tests/resources/config/config-oom new file mode 100644 index 000000000..41ed170bd Binary files /dev/null and b/tests/resources/config/config-oom differ diff --git a/tests/resources/crlf.git/HEAD b/tests/resources/crlf.git/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/crlf.git/HEAD differ diff --git a/tests/resources/crlf.git/config b/tests/resources/crlf.git/config new file mode 100644 index 000000000..a8e94d7bc Binary files /dev/null and b/tests/resources/crlf.git/config differ diff --git a/tests/resources/crlf.git/logs/HEAD b/tests/resources/crlf.git/logs/HEAD new file mode 100644 index 000000000..44f665122 Binary files /dev/null and b/tests/resources/crlf.git/logs/HEAD differ diff --git a/tests/resources/crlf.git/logs/refs/heads/master b/tests/resources/crlf.git/logs/refs/heads/master new file mode 100644 index 000000000..44f665122 Binary files /dev/null and b/tests/resources/crlf.git/logs/refs/heads/master differ diff --git a/tests/resources/crlf.git/logs/refs/remotes/origin/HEAD b/tests/resources/crlf.git/logs/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6cca8256f Binary files /dev/null and b/tests/resources/crlf.git/logs/refs/remotes/origin/HEAD differ diff --git a/tests/resources/crlf.git/objects/04/4bcd5c9bf5ebdd51e514a9a36457018f06f6e1 b/tests/resources/crlf.git/objects/04/4bcd5c9bf5ebdd51e514a9a36457018f06f6e1 new file mode 100644 index 000000000..a32a9b282 Binary files /dev/null and b/tests/resources/crlf.git/objects/04/4bcd5c9bf5ebdd51e514a9a36457018f06f6e1 differ diff --git a/tests/resources/crlf.git/objects/04/de00b358f13389948756732158eaaaefa1448c b/tests/resources/crlf.git/objects/04/de00b358f13389948756732158eaaaefa1448c new file mode 100644 index 000000000..c3b7598c0 Binary files /dev/null and b/tests/resources/crlf.git/objects/04/de00b358f13389948756732158eaaaefa1448c differ diff --git a/tests/resources/crlf.git/objects/09/7722be9b67b48dfe3b19396d02fd535300ee46 b/tests/resources/crlf.git/objects/09/7722be9b67b48dfe3b19396d02fd535300ee46 new file mode 100644 index 000000000..5c5c24cd5 Binary files /dev/null and b/tests/resources/crlf.git/objects/09/7722be9b67b48dfe3b19396d02fd535300ee46 differ diff --git a/tests/resources/crlf.git/objects/0a/a76e474d259bd7c13eb726a1396c381db55c88 b/tests/resources/crlf.git/objects/0a/a76e474d259bd7c13eb726a1396c381db55c88 new file mode 100644 index 000000000..e118d6656 Binary files /dev/null and b/tests/resources/crlf.git/objects/0a/a76e474d259bd7c13eb726a1396c381db55c88 differ diff --git a/tests/resources/crlf.git/objects/0d/06894e14df22e066763ae906e0ed3eb79c205f b/tests/resources/crlf.git/objects/0d/06894e14df22e066763ae906e0ed3eb79c205f new file mode 100644 index 000000000..b7a1f3290 Binary files /dev/null and b/tests/resources/crlf.git/objects/0d/06894e14df22e066763ae906e0ed3eb79c205f differ diff --git a/tests/resources/crlf.git/objects/0e/052888828a954ca17e5882638e3c6a083e75c0 b/tests/resources/crlf.git/objects/0e/052888828a954ca17e5882638e3c6a083e75c0 new file mode 100644 index 000000000..746143f85 Binary files /dev/null and b/tests/resources/crlf.git/objects/0e/052888828a954ca17e5882638e3c6a083e75c0 differ diff --git a/tests/resources/crlf.git/objects/0f/f5a53f19bfd2b5eea1ba550295c47515678987 b/tests/resources/crlf.git/objects/0f/f5a53f19bfd2b5eea1ba550295c47515678987 new file mode 100644 index 000000000..5366acd8c Binary files /dev/null and b/tests/resources/crlf.git/objects/0f/f5a53f19bfd2b5eea1ba550295c47515678987 differ diff --git a/tests/resources/crlf.git/objects/12/4f4293444614aa8da53be149792c2e43e9bfd9 b/tests/resources/crlf.git/objects/12/4f4293444614aa8da53be149792c2e43e9bfd9 new file mode 100644 index 000000000..29c674092 Binary files /dev/null and b/tests/resources/crlf.git/objects/12/4f4293444614aa8da53be149792c2e43e9bfd9 differ diff --git a/tests/resources/crlf.git/objects/16/78031ee023a23bd3515e4e1693b661a69f0a73 b/tests/resources/crlf.git/objects/16/78031ee023a23bd3515e4e1693b661a69f0a73 new file mode 100644 index 000000000..4aa4ffb1d Binary files /dev/null and b/tests/resources/crlf.git/objects/16/78031ee023a23bd3515e4e1693b661a69f0a73 differ diff --git a/tests/resources/crlf.git/objects/16/c72b67861f8524a5bebc05cd20472d3fca00da b/tests/resources/crlf.git/objects/16/c72b67861f8524a5bebc05cd20472d3fca00da new file mode 100644 index 000000000..e2b199458 Binary files /dev/null and b/tests/resources/crlf.git/objects/16/c72b67861f8524a5bebc05cd20472d3fca00da differ diff --git a/tests/resources/crlf.git/objects/18/c637c5d9aba6eed226ee1840cd1ca2e6c4e4c5 b/tests/resources/crlf.git/objects/18/c637c5d9aba6eed226ee1840cd1ca2e6c4e4c5 new file mode 100644 index 000000000..790eb1324 Binary files /dev/null and b/tests/resources/crlf.git/objects/18/c637c5d9aba6eed226ee1840cd1ca2e6c4e4c5 differ diff --git a/tests/resources/crlf.git/objects/20/3555c5676d75cd80d69b50beb1f4b588c59ceb b/tests/resources/crlf.git/objects/20/3555c5676d75cd80d69b50beb1f4b588c59ceb new file mode 100644 index 000000000..8038a9b10 Binary files /dev/null and b/tests/resources/crlf.git/objects/20/3555c5676d75cd80d69b50beb1f4b588c59ceb differ diff --git a/tests/resources/crlf.git/objects/23/f4582779e60bfa7f14750ad507399a58876611 b/tests/resources/crlf.git/objects/23/f4582779e60bfa7f14750ad507399a58876611 new file mode 100644 index 000000000..4a4e4dc9e Binary files /dev/null and b/tests/resources/crlf.git/objects/23/f4582779e60bfa7f14750ad507399a58876611 differ diff --git a/tests/resources/crlf.git/objects/2a/d3df895f68f4dda6a0a815c620b909bdd27c05 b/tests/resources/crlf.git/objects/2a/d3df895f68f4dda6a0a815c620b909bdd27c05 new file mode 100644 index 000000000..f5421cf6a Binary files /dev/null and b/tests/resources/crlf.git/objects/2a/d3df895f68f4dda6a0a815c620b909bdd27c05 differ diff --git a/tests/resources/crlf.git/objects/2b/55b4b94f655c857635b6a9005c056aa7de3532 b/tests/resources/crlf.git/objects/2b/55b4b94f655c857635b6a9005c056aa7de3532 new file mode 100644 index 000000000..031fd6681 Binary files /dev/null and b/tests/resources/crlf.git/objects/2b/55b4b94f655c857635b6a9005c056aa7de3532 differ diff --git a/tests/resources/crlf.git/objects/2b/d9d81b51a867352bab307b89cbb5b4a69adfe1 b/tests/resources/crlf.git/objects/2b/d9d81b51a867352bab307b89cbb5b4a69adfe1 new file mode 100644 index 000000000..96d952e85 Binary files /dev/null and b/tests/resources/crlf.git/objects/2b/d9d81b51a867352bab307b89cbb5b4a69adfe1 differ diff --git a/tests/resources/crlf.git/objects/2c/03f9f407b576eae80327864bab572e282a33ea b/tests/resources/crlf.git/objects/2c/03f9f407b576eae80327864bab572e282a33ea new file mode 100644 index 000000000..0e4afbbba Binary files /dev/null and b/tests/resources/crlf.git/objects/2c/03f9f407b576eae80327864bab572e282a33ea differ diff --git a/tests/resources/crlf.git/objects/33/cdead44e1c3ec178e39a4a69085280dbacf01b b/tests/resources/crlf.git/objects/33/cdead44e1c3ec178e39a4a69085280dbacf01b new file mode 100644 index 000000000..72dc780a0 Binary files /dev/null and b/tests/resources/crlf.git/objects/33/cdead44e1c3ec178e39a4a69085280dbacf01b differ diff --git a/tests/resources/crlf.git/objects/38/1cfe630df902bc29271a202d3277981180e4a6 b/tests/resources/crlf.git/objects/38/1cfe630df902bc29271a202d3277981180e4a6 new file mode 100644 index 000000000..0cf707296 Binary files /dev/null and b/tests/resources/crlf.git/objects/38/1cfe630df902bc29271a202d3277981180e4a6 differ diff --git a/tests/resources/crlf.git/objects/3f/96bdca0e37616026afaa325c148cec4aa62d04 b/tests/resources/crlf.git/objects/3f/96bdca0e37616026afaa325c148cec4aa62d04 new file mode 100644 index 000000000..a204fc983 Binary files /dev/null and b/tests/resources/crlf.git/objects/3f/96bdca0e37616026afaa325c148cec4aa62d04 differ diff --git a/tests/resources/crlf.git/objects/41/7786fc35b3c71aa546e3f95eb5da3c8dad8c41 b/tests/resources/crlf.git/objects/41/7786fc35b3c71aa546e3f95eb5da3c8dad8c41 new file mode 100644 index 000000000..ec57bdeba Binary files /dev/null and b/tests/resources/crlf.git/objects/41/7786fc35b3c71aa546e3f95eb5da3c8dad8c41 differ diff --git a/tests/resources/crlf.git/objects/47/fbc2c28a18df0dc773276a253eb85c7516ca50 b/tests/resources/crlf.git/objects/47/fbc2c28a18df0dc773276a253eb85c7516ca50 new file mode 100644 index 000000000..d16db9633 Binary files /dev/null and b/tests/resources/crlf.git/objects/47/fbc2c28a18df0dc773276a253eb85c7516ca50 differ diff --git a/tests/resources/crlf.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/tests/resources/crlf.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 new file mode 100644 index 000000000..adf64119a Binary files /dev/null and b/tests/resources/crlf.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ diff --git a/tests/resources/crlf.git/objects/5a/fb6a14a864e30787857dd92af837e8cdd2cb1b b/tests/resources/crlf.git/objects/5a/fb6a14a864e30787857dd92af837e8cdd2cb1b new file mode 100644 index 000000000..11a25c581 Binary files /dev/null and b/tests/resources/crlf.git/objects/5a/fb6a14a864e30787857dd92af837e8cdd2cb1b differ diff --git a/tests/resources/crlf.git/objects/68/03c385642cebc8103fddd526ef395d75678a7e b/tests/resources/crlf.git/objects/68/03c385642cebc8103fddd526ef395d75678a7e new file mode 100644 index 000000000..f8d489fcb Binary files /dev/null and b/tests/resources/crlf.git/objects/68/03c385642cebc8103fddd526ef395d75678a7e differ diff --git a/tests/resources/crlf.git/objects/69/597764abeaa1a403ebf589d2ea579c6a8f877e b/tests/resources/crlf.git/objects/69/597764abeaa1a403ebf589d2ea579c6a8f877e new file mode 100644 index 000000000..ee4f4273d Binary files /dev/null and b/tests/resources/crlf.git/objects/69/597764abeaa1a403ebf589d2ea579c6a8f877e differ diff --git a/tests/resources/crlf.git/objects/6a/e3e9c11a51f0aabebcffcbd5c00f4beed143c9 b/tests/resources/crlf.git/objects/6a/e3e9c11a51f0aabebcffcbd5c00f4beed143c9 new file mode 100644 index 000000000..6c18a3ad2 Binary files /dev/null and b/tests/resources/crlf.git/objects/6a/e3e9c11a51f0aabebcffcbd5c00f4beed143c9 differ diff --git a/tests/resources/crlf.git/objects/6b/9d5748663795f573ea857276eb2a5f8330efa0 b/tests/resources/crlf.git/objects/6b/9d5748663795f573ea857276eb2a5f8330efa0 new file mode 100644 index 000000000..680c7cd17 Binary files /dev/null and b/tests/resources/crlf.git/objects/6b/9d5748663795f573ea857276eb2a5f8330efa0 differ diff --git a/tests/resources/crlf.git/objects/6c/589757f65a970a6cc07c71c3f3d2528c611cbc b/tests/resources/crlf.git/objects/6c/589757f65a970a6cc07c71c3f3d2528c611cbc new file mode 100644 index 000000000..fe4da8ce0 Binary files /dev/null and b/tests/resources/crlf.git/objects/6c/589757f65a970a6cc07c71c3f3d2528c611cbc differ diff --git a/tests/resources/crlf.git/objects/72/10e91413baa3d9b90215e970ae53397ecc526e b/tests/resources/crlf.git/objects/72/10e91413baa3d9b90215e970ae53397ecc526e new file mode 100644 index 000000000..38c000d76 Binary files /dev/null and b/tests/resources/crlf.git/objects/72/10e91413baa3d9b90215e970ae53397ecc526e differ diff --git a/tests/resources/crlf.git/objects/77/afe26d93c49279ca90604c125496920753fede b/tests/resources/crlf.git/objects/77/afe26d93c49279ca90604c125496920753fede new file mode 100644 index 000000000..a377cb04d Binary files /dev/null and b/tests/resources/crlf.git/objects/77/afe26d93c49279ca90604c125496920753fede differ diff --git a/tests/resources/crlf.git/objects/78/db270c1841841f75a8157321bdcb50ab12e6c3 b/tests/resources/crlf.git/objects/78/db270c1841841f75a8157321bdcb50ab12e6c3 new file mode 100644 index 000000000..8a55bb082 Binary files /dev/null and b/tests/resources/crlf.git/objects/78/db270c1841841f75a8157321bdcb50ab12e6c3 differ diff --git a/tests/resources/crlf.git/objects/79/9770d1cff46753a57db7a066159b5610da6e3a b/tests/resources/crlf.git/objects/79/9770d1cff46753a57db7a066159b5610da6e3a new file mode 100644 index 000000000..5c701b867 Binary files /dev/null and b/tests/resources/crlf.git/objects/79/9770d1cff46753a57db7a066159b5610da6e3a differ diff --git a/tests/resources/crlf.git/objects/7c/ce67e58173e2b01f7db124ceaabe3183d19c49 b/tests/resources/crlf.git/objects/7c/ce67e58173e2b01f7db124ceaabe3183d19c49 new file mode 100644 index 000000000..8e836aba1 Binary files /dev/null and b/tests/resources/crlf.git/objects/7c/ce67e58173e2b01f7db124ceaabe3183d19c49 differ diff --git a/tests/resources/crlf.git/objects/85/340755cfe5e28c2835781978bb1cece91b3d0f b/tests/resources/crlf.git/objects/85/340755cfe5e28c2835781978bb1cece91b3d0f new file mode 100644 index 000000000..e83fbc290 Binary files /dev/null and b/tests/resources/crlf.git/objects/85/340755cfe5e28c2835781978bb1cece91b3d0f differ diff --git a/tests/resources/crlf.git/objects/92/0e90a663bea5d740989d5f935f6dfb473a0c5d b/tests/resources/crlf.git/objects/92/0e90a663bea5d740989d5f935f6dfb473a0c5d new file mode 100644 index 000000000..f872be6e9 Binary files /dev/null and b/tests/resources/crlf.git/objects/92/0e90a663bea5d740989d5f935f6dfb473a0c5d differ diff --git a/tests/resources/crlf.git/objects/96/87e444bcbb85645cb496080434c292f1b57182 b/tests/resources/crlf.git/objects/96/87e444bcbb85645cb496080434c292f1b57182 new file mode 100644 index 000000000..5df64d849 Binary files /dev/null and b/tests/resources/crlf.git/objects/96/87e444bcbb85645cb496080434c292f1b57182 differ diff --git a/tests/resources/crlf.git/objects/97/449da2d225557c558ac244384d487e66c3e591 b/tests/resources/crlf.git/objects/97/449da2d225557c558ac244384d487e66c3e591 new file mode 100644 index 000000000..d3917a433 Binary files /dev/null and b/tests/resources/crlf.git/objects/97/449da2d225557c558ac244384d487e66c3e591 differ diff --git a/tests/resources/crlf.git/objects/9a/6c3533fef19abd6eec8e61206b5c51982b80d9 b/tests/resources/crlf.git/objects/9a/6c3533fef19abd6eec8e61206b5c51982b80d9 new file mode 100644 index 000000000..78fc8aeb7 Binary files /dev/null and b/tests/resources/crlf.git/objects/9a/6c3533fef19abd6eec8e61206b5c51982b80d9 differ diff --git a/tests/resources/crlf.git/objects/9d/29b5bb165bf65637ffcb5ededb82ddd7c3fd13 b/tests/resources/crlf.git/objects/9d/29b5bb165bf65637ffcb5ededb82ddd7c3fd13 new file mode 100644 index 000000000..106332d55 Binary files /dev/null and b/tests/resources/crlf.git/objects/9d/29b5bb165bf65637ffcb5ededb82ddd7c3fd13 differ diff --git a/tests/resources/crlf.git/objects/a2/34455d62297f1856c4603686150c59fcb0aafe b/tests/resources/crlf.git/objects/a2/34455d62297f1856c4603686150c59fcb0aafe new file mode 100644 index 000000000..7d204f4c8 Binary files /dev/null and b/tests/resources/crlf.git/objects/a2/34455d62297f1856c4603686150c59fcb0aafe differ diff --git a/tests/resources/crlf.git/objects/a9/a2e8913c1dbe2812fac5e6b4e0a4bd5d0d5966 b/tests/resources/crlf.git/objects/a9/a2e8913c1dbe2812fac5e6b4e0a4bd5d0d5966 new file mode 100644 index 000000000..33d59f1f1 Binary files /dev/null and b/tests/resources/crlf.git/objects/a9/a2e8913c1dbe2812fac5e6b4e0a4bd5d0d5966 differ diff --git a/tests/resources/crlf.git/objects/aa/f083a9cb53dac3669dcfa0e48921580d629ec7 b/tests/resources/crlf.git/objects/aa/f083a9cb53dac3669dcfa0e48921580d629ec7 new file mode 100644 index 000000000..38775d005 Binary files /dev/null and b/tests/resources/crlf.git/objects/aa/f083a9cb53dac3669dcfa0e48921580d629ec7 differ diff --git a/tests/resources/crlf.git/objects/af/6fcf6da196f615d7cda269b55b5c4ecfb4a5b3 b/tests/resources/crlf.git/objects/af/6fcf6da196f615d7cda269b55b5c4ecfb4a5b3 new file mode 100644 index 000000000..0acc9744e Binary files /dev/null and b/tests/resources/crlf.git/objects/af/6fcf6da196f615d7cda269b55b5c4ecfb4a5b3 differ diff --git a/tests/resources/crlf.git/objects/bb/29a7b46b5d4ba3ea17b238ae561b81d59dc818 b/tests/resources/crlf.git/objects/bb/29a7b46b5d4ba3ea17b238ae561b81d59dc818 new file mode 100644 index 000000000..a08789b54 Binary files /dev/null and b/tests/resources/crlf.git/objects/bb/29a7b46b5d4ba3ea17b238ae561b81d59dc818 differ diff --git a/tests/resources/crlf.git/objects/c3/e11722855ff260bd27418988ac1467c4e9e73a b/tests/resources/crlf.git/objects/c3/e11722855ff260bd27418988ac1467c4e9e73a new file mode 100644 index 000000000..5f96dc76c Binary files /dev/null and b/tests/resources/crlf.git/objects/c3/e11722855ff260bd27418988ac1467c4e9e73a differ diff --git a/tests/resources/crlf.git/objects/c8/d0b1ebcaccdd8f968c4aae3c2175e7fed651fe b/tests/resources/crlf.git/objects/c8/d0b1ebcaccdd8f968c4aae3c2175e7fed651fe new file mode 100644 index 000000000..21e2ce093 Binary files /dev/null and b/tests/resources/crlf.git/objects/c8/d0b1ebcaccdd8f968c4aae3c2175e7fed651fe differ diff --git a/tests/resources/crlf.git/objects/cd/574f5a2baa4c79504f8837b730fa0b11defe99 b/tests/resources/crlf.git/objects/cd/574f5a2baa4c79504f8837b730fa0b11defe99 new file mode 100644 index 000000000..e8d020246 Binary files /dev/null and b/tests/resources/crlf.git/objects/cd/574f5a2baa4c79504f8837b730fa0b11defe99 differ diff --git a/tests/resources/crlf.git/objects/cd/d3dacc5c0501d5ea57bbdf90e3d80176606139 b/tests/resources/crlf.git/objects/cd/d3dacc5c0501d5ea57bbdf90e3d80176606139 new file mode 100644 index 000000000..72cf3b0fd Binary files /dev/null and b/tests/resources/crlf.git/objects/cd/d3dacc5c0501d5ea57bbdf90e3d80176606139 differ diff --git a/tests/resources/crlf.git/objects/d1/1e7ef63ba7db1db3b1b99cdbafc57a8549f8a4 b/tests/resources/crlf.git/objects/d1/1e7ef63ba7db1db3b1b99cdbafc57a8549f8a4 new file mode 100644 index 000000000..05d88fc86 Binary files /dev/null and b/tests/resources/crlf.git/objects/d1/1e7ef63ba7db1db3b1b99cdbafc57a8549f8a4 differ diff --git a/tests/resources/crlf.git/objects/dc/88e3b917de821e25962bea7ec1f55c4ce2112c b/tests/resources/crlf.git/objects/dc/88e3b917de821e25962bea7ec1f55c4ce2112c new file mode 100644 index 000000000..3db13aa79 Binary files /dev/null and b/tests/resources/crlf.git/objects/dc/88e3b917de821e25962bea7ec1f55c4ce2112c differ diff --git a/tests/resources/crlf.git/objects/de/5bfa165999d9d6c6dbafad2a7e709f93ec30fd b/tests/resources/crlf.git/objects/de/5bfa165999d9d6c6dbafad2a7e709f93ec30fd new file mode 100644 index 000000000..e288b975f Binary files /dev/null and b/tests/resources/crlf.git/objects/de/5bfa165999d9d6c6dbafad2a7e709f93ec30fd differ diff --git a/tests/resources/crlf.git/objects/e0/be8c0fa467f0a554484347c12802799d6c04fa b/tests/resources/crlf.git/objects/e0/be8c0fa467f0a554484347c12802799d6c04fa new file mode 100644 index 000000000..b6554853b Binary files /dev/null and b/tests/resources/crlf.git/objects/e0/be8c0fa467f0a554484347c12802799d6c04fa differ diff --git a/tests/resources/crlf.git/objects/e1/379fd9942d04e7e80892b866d37bdb7da9e4e1 b/tests/resources/crlf.git/objects/e1/379fd9942d04e7e80892b866d37bdb7da9e4e1 new file mode 100644 index 000000000..01f8745fd Binary files /dev/null and b/tests/resources/crlf.git/objects/e1/379fd9942d04e7e80892b866d37bdb7da9e4e1 differ diff --git a/tests/resources/crlf.git/objects/e5/062da7d7802cf492975eda580f09ac4876bd88 b/tests/resources/crlf.git/objects/e5/062da7d7802cf492975eda580f09ac4876bd88 new file mode 100644 index 000000000..62835b9e1 Binary files /dev/null and b/tests/resources/crlf.git/objects/e5/062da7d7802cf492975eda580f09ac4876bd88 differ diff --git a/tests/resources/crlf.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/crlf.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 new file mode 100644 index 000000000..711223894 Binary files /dev/null and b/tests/resources/crlf.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ diff --git a/tests/resources/crlf.git/objects/ea/030d3c6cec212069eca698cabaa5b4550f1511 b/tests/resources/crlf.git/objects/ea/030d3c6cec212069eca698cabaa5b4550f1511 new file mode 100644 index 000000000..117dc725a Binary files /dev/null and b/tests/resources/crlf.git/objects/ea/030d3c6cec212069eca698cabaa5b4550f1511 differ diff --git a/tests/resources/crlf.git/objects/ea/a6ce5bc192f4c3c19354e7434c01e4686e95d7 b/tests/resources/crlf.git/objects/ea/a6ce5bc192f4c3c19354e7434c01e4686e95d7 new file mode 100644 index 000000000..e99414387 Binary files /dev/null and b/tests/resources/crlf.git/objects/ea/a6ce5bc192f4c3c19354e7434c01e4686e95d7 differ diff --git a/tests/resources/crlf.git/objects/ef/0dcd356d77221e9c27f4f3928ad28e80b87ceb b/tests/resources/crlf.git/objects/ef/0dcd356d77221e9c27f4f3928ad28e80b87ceb new file mode 100644 index 000000000..33aceda12 Binary files /dev/null and b/tests/resources/crlf.git/objects/ef/0dcd356d77221e9c27f4f3928ad28e80b87ceb differ diff --git a/tests/resources/crlf.git/objects/f2/b745d7f47d114a3a6b31a7b628e61e804d1a58 b/tests/resources/crlf.git/objects/f2/b745d7f47d114a3a6b31a7b628e61e804d1a58 new file mode 100644 index 000000000..7b2e7a116 Binary files /dev/null and b/tests/resources/crlf.git/objects/f2/b745d7f47d114a3a6b31a7b628e61e804d1a58 differ diff --git a/tests/resources/crlf.git/objects/f4/d25b796d86387205a5498175d66e91d1e5006a b/tests/resources/crlf.git/objects/f4/d25b796d86387205a5498175d66e91d1e5006a new file mode 100644 index 000000000..792b1659d Binary files /dev/null and b/tests/resources/crlf.git/objects/f4/d25b796d86387205a5498175d66e91d1e5006a differ diff --git a/tests/resources/crlf.git/objects/fa/1385d99a319b43c06f5309d1aae9fdd3adea46 b/tests/resources/crlf.git/objects/fa/1385d99a319b43c06f5309d1aae9fdd3adea46 new file mode 100644 index 000000000..d0dda458e Binary files /dev/null and b/tests/resources/crlf.git/objects/fa/1385d99a319b43c06f5309d1aae9fdd3adea46 differ diff --git a/tests/resources/crlf.git/objects/fe/085d9ace90cc675b87df15e1aeed0c3a31407f b/tests/resources/crlf.git/objects/fe/085d9ace90cc675b87df15e1aeed0c3a31407f new file mode 100644 index 000000000..2e8d10b76 Binary files /dev/null and b/tests/resources/crlf.git/objects/fe/085d9ace90cc675b87df15e1aeed0c3a31407f differ diff --git a/tests/resources/crlf.git/objects/fe/ab3713c4659bb22700042b3c55b8d60d0a952b b/tests/resources/crlf.git/objects/fe/ab3713c4659bb22700042b3c55b8d60d0a952b new file mode 100644 index 000000000..8552c7bf7 Binary files /dev/null and b/tests/resources/crlf.git/objects/fe/ab3713c4659bb22700042b3c55b8d60d0a952b differ diff --git a/tests/resources/crlf.git/packed-refs b/tests/resources/crlf.git/packed-refs new file mode 100644 index 000000000..33446e79f Binary files /dev/null and b/tests/resources/crlf.git/packed-refs differ diff --git a/tests/resources/crlf.git/refs/heads/empty-files b/tests/resources/crlf.git/refs/heads/empty-files new file mode 100644 index 000000000..8f1fe6104 Binary files /dev/null and b/tests/resources/crlf.git/refs/heads/empty-files differ diff --git a/tests/resources/crlf.git/refs/heads/master b/tests/resources/crlf.git/refs/heads/master new file mode 100644 index 000000000..97c85bca7 Binary files /dev/null and b/tests/resources/crlf.git/refs/heads/master differ diff --git a/tests/resources/crlf.git/refs/remotes/origin/HEAD b/tests/resources/crlf.git/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff Binary files /dev/null and b/tests/resources/crlf.git/refs/remotes/origin/HEAD differ diff --git a/tests/resources/testrepo.git/refs/blobs/annotated_tag_to_blob b/tests/resources/testrepo.git/refs/blobs/annotated_tag_to_blob new file mode 100644 index 000000000..6c146d6e3 Binary files /dev/null and b/tests/resources/testrepo.git/refs/blobs/annotated_tag_to_blob differ diff --git a/tests/resources/testrepo/.gitted/refs/symref b/tests/resources/testrepo/.gitted/refs/symref new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/testrepo/.gitted/refs/symref differ diff --git a/tests/revert/bare.c b/tests/revert/bare.c index fc7d03065..03cffbf3e 100644 --- a/tests/revert/bare.c +++ b/tests/revert/bare.c @@ -2,7 +2,7 @@ #include "clar_libgit2.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" #include "git2/revert.h" #include "../merge/merge_helpers.h" diff --git a/tests/revert/workdir.c b/tests/revert/workdir.c index 9acf20d6f..2ad059d99 100644 --- a/tests/revert/workdir.c +++ b/tests/revert/workdir.c @@ -2,7 +2,7 @@ #include "clar_libgit2.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" #include "git2/revert.h" #include "../merge/merge_helpers.h" diff --git a/tests/revwalk/basic.c b/tests/revwalk/basic.c index 8e7302552..ee70ec635 100644 --- a/tests/revwalk/basic.c +++ b/tests/revwalk/basic.c @@ -400,6 +400,24 @@ void test_revwalk_basic__push_range(void) cl_git_pass(test_walk_only(_walk, commit_sorting_segment, 2)); } +void test_revwalk_basic__push_range_merge_base(void) +{ + revwalk_basic_setup_walk(NULL); + + git_revwalk_reset(_walk); + git_revwalk_sorting(_walk, 0); + cl_git_fail_with(GIT_EINVALIDSPEC, git_revwalk_push_range(_walk, "HEAD...HEAD~2")); +} + +void test_revwalk_basic__push_range_no_range(void) +{ + revwalk_basic_setup_walk(NULL); + + git_revwalk_reset(_walk); + git_revwalk_sorting(_walk, 0); + cl_git_fail_with(GIT_EINVALIDSPEC, git_revwalk_push_range(_walk, "HEAD")); +} + void test_revwalk_basic__push_mixed(void) { git_oid oid; diff --git a/tests/stash/apply.c b/tests/stash/apply.c index 063223ae6..5eb59545e 100644 --- a/tests/stash/apply.c +++ b/tests/stash/apply.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "stash_helpers.h" static git_signature *signature; diff --git a/tests/stash/drop.c b/tests/stash/drop.c index 89a0ade72..6b0895ba8 100644 --- a/tests/stash/drop.c +++ b/tests/stash/drop.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "stash_helpers.h" #include "refs.h" diff --git a/tests/stash/foreach.c b/tests/stash/foreach.c index 57dc8eeb4..fa3a9c906 100644 --- a/tests/stash/foreach.c +++ b/tests/stash/foreach.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "stash_helpers.h" struct callback_data diff --git a/tests/stash/save.c b/tests/stash/save.c index c38ef8274..d568567d5 100644 --- a/tests/stash/save.c +++ b/tests/stash/save.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "stash_helpers.h" static git_repository *repo; @@ -283,6 +283,26 @@ void test_stash_save__stashing_updates_the_reflog(void) assert_object_oid("refs/stash@{1}", NULL, GIT_OBJECT_COMMIT); } +void test_stash_save__multiline_message(void) +{ + const char *msg = "This\n\nis a multiline message\n"; + const git_reflog_entry *entry; + git_reflog *reflog; + + assert_object_oid("refs/stash@{0}", NULL, GIT_OBJECT_COMMIT); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, msg, GIT_STASH_DEFAULT)); + + cl_git_pass(git_reflog_read(&reflog, repo, "refs/stash")); + cl_assert(entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_s(git_reflog_entry_message(entry), "On master: This is a multiline message"); + + assert_object_oid("refs/stash@{0}", git_oid_tostr_s(&stash_tip_oid), GIT_OBJECT_COMMIT); + assert_commit_message_contains("refs/stash@{0}", msg); + + git_reflog_free(reflog); +} + void test_stash_save__cannot_stash_when_there_are_no_local_change(void) { git_index *index; diff --git a/tests/stash/stash_helpers.c b/tests/stash/stash_helpers.c index 0398757c2..cd0cfbd0f 100644 --- a/tests/stash/stash_helpers.c +++ b/tests/stash/stash_helpers.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "stash_helpers.h" void setup_stash(git_repository *repo, git_signature *signature) diff --git a/tests/status/submodules.c b/tests/status/submodules.c index 33c9e5ab4..12edce2b2 100644 --- a/tests/status/submodules.c +++ b/tests/status/submodules.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "status_helpers.h" #include "../submodule/submodule_helpers.h" diff --git a/tests/status/worktree.c b/tests/status/worktree.c index 4c37a337c..7711b2da4 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -1,5 +1,5 @@ #include "clar_libgit2.h" -#include "fileops.h" +#include "futils.h" #include "ignore.h" #include "status_data.h" #include "posix.h" diff --git a/tests/status/worktree_init.c b/tests/status/worktree_init.c index 9d5cfa5a3..40f1b2a31 100644 --- a/tests/status/worktree_init.c +++ b/tests/status/worktree_init.c @@ -1,7 +1,7 @@ #include "clar_libgit2.h" #include "git2/sys/repository.h" -#include "fileops.h" +#include "futils.h" #include "ignore.h" #include "status_helpers.h" #include "posix.h" diff --git a/tests/submodule/add.c b/tests/submodule/add.c index d5886776d..f4d1e3b79 100644 --- a/tests/submodule/add.c +++ b/tests/submodule/add.c @@ -3,8 +3,9 @@ #include "path.h" #include "submodule_helpers.h" #include "config/config_helpers.h" -#include "fileops.h" +#include "futils.h" #include "repository.h" +#include "git2/sys/commit.h" static git_repository *g_repo = NULL; static const char *valid_blob_id = "fa49b077972391ad58037050f2a75f74e3671e92"; @@ -183,3 +184,68 @@ void test_submodule_add__file_exists_in_index(void) git_submodule_free(sm); git_buf_dispose(&name); } + +void test_submodule_add__submodule_clone(void) +{ + git_oid tree_id, commit_id; + git_signature *sig; + git_submodule *sm; + git_index *index; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + /* Create the submodule structure, clone into it and finalize */ + cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "testrepo-add", true)); + cl_git_pass(git_submodule_clone(NULL, sm, NULL)); + cl_git_pass(git_submodule_add_finalize(sm)); + + /* Create the submodule commit */ + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_signature_now(&sig, "Submoduler", "submoduler@local")); + cl_git_pass(git_commit_create_from_ids(&commit_id, g_repo, "HEAD", sig, sig, NULL, "A submodule\n", + &tree_id, 0, NULL)); + + assert_submodule_exists(g_repo, "testrepo-add"); + + git_signature_free(sig); + git_submodule_free(sm); + git_index_free(index); +} + +void test_submodule_add__submodule_clone_into_nonempty_dir_succeeds(void) +{ + git_submodule *sm; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(p_mkdir("empty_standard_repo/sm", 0777)); + cl_git_mkfile("empty_standard_repo/sm/foobar", ""); + + /* Create the submodule structure, clone into it and finalize */ + cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "sm", true)); + cl_git_pass(git_submodule_clone(NULL, sm, NULL)); + cl_git_pass(git_submodule_add_finalize(sm)); + + cl_assert(git_path_exists("empty_standard_repo/sm/foobar")); + + assert_submodule_exists(g_repo, "sm"); + + git_submodule_free(sm); +} + +void test_submodule_add__submodule_clone_twice_fails(void) +{ + git_submodule *sm; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + /* Create the submodule structure, clone into it and finalize */ + cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "sm", true)); + cl_git_pass(git_submodule_clone(NULL, sm, NULL)); + cl_git_pass(git_submodule_add_finalize(sm)); + + cl_git_fail(git_submodule_clone(NULL, sm, NULL)); + + git_submodule_free(sm); +} diff --git a/tests/submodule/escape.c b/tests/submodule/escape.c index c36874296..08eb76809 100644 --- a/tests/submodule/escape.c +++ b/tests/submodule/escape.c @@ -2,7 +2,7 @@ #include "posix.h" #include "path.h" #include "submodule_helpers.h" -#include "fileops.h" +#include "futils.h" #include "repository.h" static git_repository *g_repo = NULL; diff --git a/tests/submodule/init.c b/tests/submodule/init.c index 84143e18f..a1d870b9e 100644 --- a/tests/submodule/init.c +++ b/tests/submodule/init.c @@ -2,7 +2,7 @@ #include "posix.h" #include "path.h" #include "submodule_helpers.h" -#include "fileops.h" +#include "futils.h" static git_repository *g_repo = NULL; diff --git a/tests/submodule/inject_option.c b/tests/submodule/inject_option.c index 182f088be..cfc02acff 100644 --- a/tests/submodule/inject_option.c +++ b/tests/submodule/inject_option.c @@ -2,7 +2,7 @@ #include "posix.h" #include "path.h" #include "submodule_helpers.h" -#include "fileops.h" +#include "futils.h" #include "repository.h" static git_repository *g_repo = NULL; diff --git a/tests/submodule/lookup.c b/tests/submodule/lookup.c index 8bab1b93a..6f7506d2c 100644 --- a/tests/submodule/lookup.c +++ b/tests/submodule/lookup.c @@ -2,7 +2,7 @@ #include "submodule_helpers.h" #include "git2/sys/repository.h" #include "repository.h" -#include "fileops.h" +#include "futils.h" static git_repository *g_repo = NULL; diff --git a/tests/submodule/modify.c b/tests/submodule/modify.c index f7a089e72..654f677e6 100644 --- a/tests/submodule/modify.c +++ b/tests/submodule/modify.c @@ -210,3 +210,24 @@ void test_submodule_modify__set_url(void) cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm)); git_submodule_free(sm); } + +void test_submodule_modify__set_relative_url(void) +{ + git_buf path = GIT_BUF_INIT; + git_repository *repo; + git_submodule *sm; + + cl_git_pass(git_submodule_set_url(g_repo, SM1, "../relative-url")); + cl_git_pass(git_submodule_lookup(&sm, g_repo, SM1)); + cl_git_pass(git_submodule_sync(sm)); + cl_git_pass(git_submodule_open(&repo, sm)); + + cl_git_pass(git_buf_joinpath(&path, clar_sandbox_path(), "relative-url")); + + assert_config_entry_value(g_repo, "submodule."SM1".url", path.ptr); + assert_config_entry_value(repo, "remote.origin.url", path.ptr); + + git_repository_free(repo); + git_submodule_free(sm); + git_buf_dispose(&path); +} diff --git a/tests/submodule/nosubs.c b/tests/submodule/nosubs.c index ca2d1d693..e47ee3983 100644 --- a/tests/submodule/nosubs.c +++ b/tests/submodule/nosubs.c @@ -2,7 +2,7 @@ #include "clar_libgit2.h" #include "posix.h" -#include "fileops.h" +#include "futils.h" void test_submodule_nosubs__cleanup(void) { diff --git a/tests/submodule/repository_init.c b/tests/submodule/repository_init.c index 7dd97bac0..9962af311 100644 --- a/tests/submodule/repository_init.c +++ b/tests/submodule/repository_init.c @@ -3,7 +3,7 @@ #include "path.h" #include "submodule_helpers.h" #include "config/config_helpers.h" -#include "fileops.h" +#include "futils.h" static git_repository *g_repo = NULL; diff --git a/tests/submodule/status.c b/tests/submodule/status.c index 41fc17387..06595cc9a 100644 --- a/tests/submodule/status.c +++ b/tests/submodule/status.c @@ -2,7 +2,7 @@ #include "posix.h" #include "path.h" #include "submodule_helpers.h" -#include "fileops.h" +#include "futils.h" #include "iterator.h" static git_repository *g_repo = NULL; diff --git a/tests/submodule/update.c b/tests/submodule/update.c index 08a279a48..79353e575 100644 --- a/tests/submodule/update.c +++ b/tests/submodule/update.c @@ -2,7 +2,7 @@ #include "posix.h" #include "path.h" #include "submodule_helpers.h" -#include "fileops.h" +#include "futils.h" static git_repository *g_repo = NULL; diff --git a/tests/trace/windows/stacktrace.c b/tests/trace/windows/stacktrace.c index ca5760edd..756ac68a3 100644 --- a/tests/trace/windows/stacktrace.c +++ b/tests/trace/windows/stacktrace.c @@ -132,7 +132,7 @@ static void aux_cb_alloc__1(unsigned int *aux_id) *aux_id = aux_counter++; } -static void aux_cb_lookup__1(unsigned int aux_id, char *aux_msg, unsigned int aux_msg_len) +static void aux_cb_lookup__1(unsigned int aux_id, char *aux_msg, size_t aux_msg_len) { p_snprintf(aux_msg, aux_msg_len, "\tQQ%08x\n", aux_id); } diff --git a/tests/win32/longpath.c b/tests/win32/longpath.c index bf5aac724..80ae08d8b 100644 --- a/tests/win32/longpath.c +++ b/tests/win32/longpath.c @@ -3,7 +3,7 @@ #include "git2/clone.h" #include "clone.h" #include "buffer.h" -#include "fileops.h" +#include "futils.h" static git_buf path = GIT_BUF_INIT; diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 73a6a0193..5e99dbf61 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -42,7 +42,7 @@ void test_worktree_worktree__list_with_invalid_worktree_dirs(void) }; git_buf path = GIT_BUF_INIT; git_strarray wts; - unsigned i, j, len; + size_t i, j, len; cl_git_pass(git_buf_printf(&path, "%s/worktrees/invalid", fixture.repo->commondir)); @@ -604,8 +604,8 @@ void test_worktree_worktree__foreach_head_gives_same_results_in_wt_and_repo(void cl_git_pass(git_reference_lookup(&heads[0], fixture.repo, GIT_HEAD_FILE)); cl_git_pass(git_reference_lookup(&heads[1], fixture.worktree, GIT_HEAD_FILE)); - cl_git_pass(git_repository_foreach_head(fixture.repo, read_head_ref, &repo_refs)); - cl_git_pass(git_repository_foreach_head(fixture.worktree, read_head_ref, &worktree_refs)); + cl_git_pass(git_repository_foreach_head(fixture.repo, read_head_ref, 0, &repo_refs)); + cl_git_pass(git_repository_foreach_head(fixture.worktree, read_head_ref, 0, &worktree_refs)); cl_assert_equal_i(repo_refs.length, ARRAY_SIZE(heads)); cl_assert_equal_i(worktree_refs.length, ARRAY_SIZE(heads));